Leetcode-64.[最小路径和]

23 阅读3分钟

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

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

示例 1:

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

示例 2:

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

核心是从网格左上角走到右下角(只能向右 / 向下走),求路径上数字之和的最小值 ,考虑动态规划算法。

动态规划算法的两个关键特征:

  1. 最优子结构:到达位置 (i,j) 的最小路径和,只依赖于「到达上方 (i-1,j) 的最小路径和」和「到达左方 (i,j-1) 的最小路径和」(因为只能向右 / 向下走,没有其他来源);
  2. 无后效性:一旦算出 (i,j) 的最小路径和,后续计算不会改变这个值(路径选择不回头)。

动态规划的核心思路(四步走)

步骤 1:定义状态(最关键的一步)

设 dp[i][j] 表示「从左上角 (0,0) 走到 (i,j) 的最小路径和」。目标就是求 dp[m-1][n-1](m 是行数,n 是列数)。

步骤 2:推导状态转移方程

因为只能从「上方 (i-1,j)」或「左方 (i,j-1)」走到 (i,j),所以:dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j](走到 (i,j) 的最小和 = 上 / 左中更小的那个 + 当前格子的数值)

步骤 3:处理边界条件

  • 第一行(i=0):只能从左边走过来,所以 dp[0][j] = dp[0][j-1] + grid[0][j]
  • 第一列(j=0):只能从上面走过来,所以 dp[i][0] = dp[i-1][0] + grid[i][0]
  • 起点(0,0):dp[0][0] = grid[0][0](只有自己)。

步骤 4:确定遍历顺序

因为 dp[i][j] 依赖左 / 上的结果,所以按「从上到下、从左到右」遍历网格即可。

代码实现:

1.基础 DP(易理解,用二维数组)

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if (grid.empty() || grid[0].empty()) return 0;
        int m = grid.size(), n = grid[0].size();
        // 定义dp数组,和网格同大小
        vector<vector<int>> dp(m, vector<int>(n, 0));
        
        // 初始化起点
        dp[0][0] = grid[0][0];
        
        // 初始化第一列(只能从上到下)
        for (int i = 1; i < m; ++i) {
            dp[i][0] = dp[i-1][0] + grid[i][0];
        }
        
        // 初始化第一行(只能从左到右)
        for (int j = 1; j < n; ++j) {
            dp[0][j] = dp[0][j-1] + grid[0][j];
        }
        
        // 遍历网格,填充dp数组
        for (int i = 1; i < m; ++i) {
            for (int j = 1; j < n; ++j) {
                dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
            }
        }
        
        return dp[m-1][n-1];
    }
};

2.空间优化(进阶,O (1) 额外空间)

观察发现:dp[i][j] 只依赖左 / 上的值,且可以直接在原网格上修改(覆盖),无需额外 dp 数组:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if (grid.empty() || grid[0].empty()) return 0;
        int m = grid.size(), n = grid[0].size();
        
        // 初始化第一列
        for (int i = 1; i < m; ++i) {
            grid[i][0] += grid[i-1][0];
        }
        
        // 初始化第一行
        for (int j = 1; j < n; ++j) {
            grid[0][j] += grid[0][j-1];
        }
        
        // 直接在原数组上计算
        for (int i = 1; i < m; ++i) {
            for (int j = 1; j < n; ++j) {
                grid[i][j] += min(grid[i-1][j], grid[i][j-1]);
            }
        }
        
        return grid[m-1][n-1];
    }
};

以后遇到类似的「网格路径 + 最值」问题,按这个流程想:

  1. 看移动规则:能走哪些方向(本题只有右 / 下)→ 确定状态转移的来源;
  2. 定义状态dp[i][j] 表示 “到 (i,j) 的目标值(和 / 步数 / 数量)”;
  3. 推转移方程:从来源中选最优(min/max)+ 当前值;
  4. 处理边界:第一行 / 第一列(只能单方向走)。