64. 最小路径和 (Medium)

专题归类: 10-动态规划 LeetCode 链接: https://leetcode.cn/problems/minimum-path-sum/


在线做题: 写 Python 代码并运行 · Java 跳转 LeetCode

题目描述

给定一个包含非负整数的 m x n 网格 grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明: 每次只能向下或者向右移动一步。

示例 1:

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 200
  • 0 <= grid[i][j] <= 200

题目详细分析

  • 数据范围:m, n <= 200,网格值范围为 [0, 200]。O(m * n) 的 DP 足够。
  • 核心约束:只能向右或向下,不能向左或向上。网格值非负,意味着路径和不会随着路径缩短而减少。
  • 边界条件:左上角是起点,路径和 = grid[0][0];第一行只能从左边来;第一列只能从上面来。
  • 隐藏条件:由于非负约束,贪心思想(局部最小)不一定等于全局最小,必须用 DP 保证全局最优。

小白版直白理解

你要从地图左上角走到右下角,每经过一个格子要付”过路费”(格子里的数字),每次只能往右或往下走。你想找一条过路费总和最小的路线。这就像玩游戏时规划最省钱的路线——到达某个路口的最小费用 = 这个路口的过路费 + min(从左边来的总费用, 从上面来的总费用)。


解题思路

思路一:二维 DP(推荐)

核心洞察:到达 (i,j) 的最小路径和 = grid[i][j] + min(从上方来的路径和, 从左方来的路径和)。第一行只能从左来,第一列只能从上来。

DP 五步法:

  1. dp 定义dp[i][j] 表示到达 (i,j) 的最小路径和
  2. 递推公式dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
  3. 初始化dp[0][0] = grid[0][0];第一行:dp[0][j] = dp[0][j-1] + grid[0][j];第一列:dp[i][0] = dp[i-1][0] + grid[i][0]
  4. 遍历顺序:从左到右,从上到下
  5. 举例验证:grid=[[1,3,1],[1,5,1],[4,2,1]] → dp[0]=[1,4,5]; dp[1]=[2,7,6]; dp[2]=[6,8,7] ✓
def minPathSum(grid):
    m, n = len(grid), len(grid[0])
    dp = [[0] * n for _ in range(m)]
 
    dp[0][0] = grid[0][0]
    # 初始化第一行
    for j in range(1, n):
        dp[0][j] = dp[0][j - 1] + grid[0][j]
    # 初始化第一列
    for i in range(1, m):
        dp[i][0] = dp[i - 1][0] + grid[i][0]
 
    # DP 递推
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1])
 
    return dp[m - 1][n - 1]

思路二:原地修改(空间 O(1))

直接在原 grid 上修改,不申请额外空间。

def minPathSum(grid):
    m, n = len(grid), len(grid[0])
 
    for i in range(m):
        for j in range(n):
            if i == 0 and j == 0:
                continue
            elif i == 0:                     # 第一行
                grid[i][j] += grid[i][j - 1]
            elif j == 0:                     # 第一列
                grid[i][j] += grid[i - 1][j]
            else:                            # 中间格子
                grid[i][j] += min(grid[i - 1][j], grid[i][j - 1])
 
    return grid[m - 1][n - 1]

思路三:一维 DP(滚动数组优化)

与不同路径类似,用一维数组滚动更新。

def minPathSum(grid):
    m, n = len(grid), len(grid[0])
    dp = [float('inf')] * n
    dp[0] = 0
 
    for i in range(m):
        dp[0] += grid[i][0]                  # 每行第一个只能从上面来
        for j in range(1, n):
            # dp[j] 来自上方(旧值),dp[j-1] 来自左方(新值)
            dp[j] = grid[i][j] + min(dp[j], dp[j - 1])
 
    return dp[-1]

易错点

  • 初始化顺序:必须先初始化 dp[0][0] 和第一行、第一列,再递推中间部分。
  • 原地修改副作用:如果外部需要保留原 grid 数据,不要用原地修改。
  • 一维 DP 的 dp[0] 处理:每行开头 dp[0] += grid[i][0] 不能忘。
  • 索引越界:创建定长数组后,小心 m, n 为 1 的边界情况。

框架提炼

二维最值 DP 模板(依赖上方和左方):

def grid_path_min(grid):
    m, n = len(grid), len(grid[0])
    dp = [[0] * n for _ in range(m)]
 
    # 初始化第一行第一列
    dp[0][0] = grid[0][0]
    for j in range(1, n):
        dp[0][j] = dp[0][j-1] + grid[0][j]
    for i in range(1, m):
        dp[i][0] = dp[i-1][0] + grid[i][0]
 
    # 递推
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
 
    return dp[m-1][n-1]

这种”左上到右下”的网格 DP 结构非常通用,核心是处理第一行和第一列的边界。


关联题目