【力扣100题】52.最小路径和

题目描述

给定一个包含非负整数的 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
解释:路径 1 -> 2 -> 3 -> 6 或 1 -> 4 -> 5 -> 6

解题思路总览

方法 核心思想 时间复杂度 空间复杂度 备注
暴力搜索 递归尝试所有路径,记录最小和 O(2^(m+n)) O(m+n) 会超时
记忆化搜索 递归 + 备忘录,避免重复计算 O(m*n) O(m*n) 剪枝优化
动态规划 自底向上填表 O(m*n) O(m*n) 最常用
空间优化 一维数组滚动 O(m*n) O(n) 面试最优

一、动态规划(最常用)

思路

定义 dp[i][j] = 从 (0,0) 到 (i,j) 的最小路径和。

状态转移方程

  • 机器人只能从上方 (i-1,j) 或左方 (i,j-1) 到达 (i,j)
  • 取两者中路径和较小的那个,加上当前格子的值
  • dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])

边界条件

  • 第一列 dp[i][0] = dp[i-1][0] + grid[i][0](只能从上往下走)
  • 第一行 dp[0][j] = dp[0][j-1] + grid[0][j](只能从左往右走)

完整代码

cpp 复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int 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];
        }

        // 填表:取上方和左方的较小值,加上当前格子
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1]);
            }
        }

        return dp[m - 1][n - 1];
    }
};

二、算法流程图

复制代码
以 grid = [[1,3,1],[1,5,1],[4,2,1]] 为例(3行3列):

原始网格:
     j:0   1   2
    +---+---+---+
i:0 | 1 | 3 | 1 |
    +---+---+---+
i:1 | 1 | 5 | 1 |
    +---+---+---+
i:2 | 4 | 2 | 1 |
    +---+---+---+

Step 1: 初始化 dp 数组(全部置 0)

     j:0   1   2
    +---+---+---+
i:0 | 0 | 0 | 0 |
    +---+---+---+
i:1 | 0 | 0 | 0 |
    +---+---+---+
i:2 | 0 | 0 | 0 |
    +---+---+---+

Step 2: 原点 dp[0][0] = grid[0][0] = 1

     j:0   1   2
    +---+---+---+
i:0 | 1 | 0 | 0 |
    +---+---+---+
i:1 | 0 | 0 | 0 |
    +---+---+---+
i:2 | 0 | 0 | 0 |
    +---+---+---+

Step 3: 初始化第一列 dp[i][0] = dp[i-1][0] + grid[i][0]

dp[1][0] = dp[0][0] + grid[1][0] = 1 + 1 = 2
dp[2][0] = dp[1][0] + grid[2][0] = 2 + 4 = 6

     j:0   1   2
    +---+---+---+
i:0 | 1 | 0 | 0 |
    +---+---+---+
i:1 | 2 | 0 | 0 |
    +---+---+---+
i:2 | 6 | 0 | 0 |
    +---+---+---+

Step 4: 初始化第一行 dp[0][j] = dp[0][j-1] + grid[0][j]

dp[0][1] = dp[0][0] + grid[0][1] = 1 + 3 = 4
dp[0][2] = dp[0][1] + grid[0][2] = 4 + 1 = 5

     j:0   1   2
    +---+---+---+
i:0 | 1 | 4 | 5 |
    +---+---+---+
i:1 | 2 | 0 | 0 |
    +---+---+---+
i:2 | 6 | 0 | 0 |
    +---+---+---+

Step 5: 填表 dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])

dp[1][1] = grid[1][1] + min(dp[0][1], dp[1][0]) = 5 + min(4, 2) = 7
dp[1][2] = grid[1][2] + min(dp[0][2], dp[1][1]) = 1 + min(5, 7) = 6
dp[2][1] = grid[2][1] + min(dp[1][1], dp[2][0]) = 2 + min(7, 6) = 8
dp[2][2] = grid[2][2] + min(dp[1][2], dp[2][1]) = 1 + min(6, 8) = 7

最终 dp:
     j:0   1   2
    +---+---+---+
i:0 | 1 | 4 | 5 |
    +---+---+---+
i:1 | 2 | 7 | 6 |
    +---+---+---+
i:2 | 6 | 8 | 7 |
    +---+---+---+

答案:dp[2][2] = 7  OK

路径追溯:1 -> 3 -> 1 -> 1 -> 1 = 7

三、空间优化(面试最优解)

思路

第 i 行只依赖第 i-1 行,可以用一个一维数组完成。

cpp 复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        vector<int> dp(n, 0);

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 && j == 0) {
                    dp[j] = grid[i][j];  // 原点
                } else if (i == 0) {
                    dp[j] = dp[j - 1] + grid[i][j];  // 第一行,只能从左来
                } else if (j == 0) {
                    dp[j] = dp[j] + grid[i][j];  // 第一列,只能从上来
                } else {
                    dp[j] = grid[i][j] + min(dp[j], dp[j - 1]);  // 取较小者
                }
            }
        }

        return dp[n - 1];
    }
};

图解空间优化

复制代码
以 grid = [[1,3,1],[1,5,1],[4,2,1]] 为例:

初始化 dp = [0, 0, 0]

处理 i=0(第一行):
  j=0: dp[0] = 1                            -> [1, 0, 0]
  j=1: dp[1] = dp[0] + 3 = 1 + 3 = 4        -> [1, 4, 0]
  j=2: dp[2] = dp[1] + 1 = 4 + 1 = 5        -> [1, 4, 5]

处理 i=1(第二行):
  j=0: dp[0] = dp[0] + 1 = 1 + 1 = 2        -> [2, 4, 5]
  j=1: dp[1] = 5 + min(4, 2) = 5 + 2 = 7   -> [2, 7, 5]
  j=2: dp[2] = 1 + min(5, 7) = 1 + 5 = 6   -> [2, 7, 6]

处理 i=2(第三行):
  j=0: dp[0] = dp[0] + 4 = 2 + 4 = 6        -> [6, 7, 6]
  j=1: dp[1] = 2 + min(7, 6) = 2 + 6 = 8   -> [6, 8, 6]
  j=2: dp[2] = 1 + min(6, 8) = 1 + 6 = 7   -> [6, 8, 7]

答案:dp[2] = 7  OK

时间复杂度:O(m*n)
空间复杂度:O(n)


四、进一步优化:原地修改 grid

思路

既然 dp 只依赖 grid,可以直接在 grid 上修改,不额外占用空间。

cpp 复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 && j == 0) continue;  // 原点,跳过

                if (i == 0) {
                    // 第一行,只能从左来
                    grid[i][j] += grid[i][j - 1];
                } else if (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];
    }
};

空间复杂度:O(1)(仅用了常数个变量)


五、记忆化搜索(递归版)

思路

从终点往回递归,每个格子选择来自上方或左方的较小路径和。

cpp 复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        vector<vector<int>> memo(m, vector<int>(n, -1));
        return dfs(m - 1, n - 1, grid, memo);
    }

private:
    int dfs(int i, int j, vector<vector<int>>& grid, vector<vector<int>>& memo) {
        // 到达原点
        if (i == 0 && j == 0) return grid[0][0];

        // 之前计算过
        if (memo[i][j] != -1) return memo[i][j];

        int ans = 0;
        if (i == 0) {
            // 在第一行,只能从左来
            ans = dfs(i, j - 1, grid, memo) + grid[i][j];
        } else if (j == 0) {
            // 在第一列,只能从上来
            ans = dfs(i - 1, j, grid, memo) + grid[i][j];
        } else {
            // 取上方和左方的较小者
            ans = grid[i][j] + min(dfs(i - 1, j, grid, memo), dfs(i, j - 1, grid, memo));
        }

        memo[i][j] = ans;
        return ans;
    }
};

六、逐行解析(对照原题代码)

cpp 复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();       // 行数
        int n = grid[0].size();    // 列数

        // 定义二维 dp 数组,m 行 n 列
        vector<vector<int>> dp(m, vector<int>(n, 0));

        // 原点:最小路径和就是 grid[0][0] 本身
        dp[0][0] = grid[0][0];

        // 初始化第一列:只能从上往下走
        // dp[i][0] = dp[i-1][0] + grid[i][0]
        for (int i = 1; i < m; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }

        // 初始化第一行:只能从左往右走
        // dp[0][j] = dp[0][j-1] + grid[0][j]
        for (int i = 1; i < n; i++) {  // 注意:原题变量名用的是 i,实际是列索引
            dp[0][i] = dp[0][i - 1] + grid[0][i];
        }

        // 填表:对于其他位置,取上方和左方的较小值
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1]);
            }
        }

        // 返回终点的最小路径和
        return dp[m - 1][n - 1];
    }
};

七、与第62题对比

维度 第62题 不同路径 第64题 最小路径和
目标 计数(多少条) 求和(最小和)
初始化 dpi0 = 1, dp0j = 1 dpi0 += gridi0, dp0j += grid0j
转移方程 dpij = dpi-1j + dpij-1 dpij = gridij + min(dpi-1j, dpij-1)
边界 第一行/列为 1 第一行/列为累计和

复杂度分析总结

方法 时间复杂度 空间复杂度 备注
暴力搜索 O(2^(m+n)) O(m+n) 会超时
记忆化搜索 O(m*n) O(m*n) 剪枝优化
动态规划 O(m*n) O(m*n) 最常用
空间优化 O(m*n) O(n) 面试最优
原地修改 O(m*n) O(1) 最优空间

面试追问 FAQ

问题 回答要点
Q: 为什么用 min 而不是加法? 因为题目要求最小路径和,所以每次选择使路径和更小的方向
Q: 如果网格中有负数怎么办? 同样用 DP,负数会被正确处理(因为 min 会选择更小的负数路径)
Q: 空间还能更优化吗? 可以原地修改 grid,空间降到 O(1),但会修改输入数据
Q: 能否用 BFS? BFS 可以,但需要维护最小堆(或每步都更新),复杂度高,不推荐
Q: 如何输出最小路径? 除了 dp 数组,再用一个 path 数组记录每步的选择方向,最后回溯

相关题目

题目编号 题目名称 难度 核心差异
62 不同路径 中等 计数问题,无权重
63 不同路径 II 中等 有障碍物
64 最小路径和 中等 带权重的最小路径和
120 三角形最小路径和 中等 三角形网格,从顶到底
174 地下城游戏 困难 从右下向左上 DP,求最小生命值
931 下降路径最小和 中等 矩阵,可向左下/正下/右下走

总结

要点 内容
核心思想 动态规划,每个位置由上方或左方转移而来
状态定义 dpij = 从 (0,0) 到 (i,j) 的最小路径和
转移方程 dpij = gridij + min(dpi-1j, dpij-1)
初始化 原点 dp00 = grid00,第一行/列是累计和
空间优化 一维数组 O(n) 或原地修改 O(1)
与62题对比 计数变求和,+1 变 +min

这道题是第62题(不同路径)的进阶版,将"计数"变为"求最小和"。掌握后可以拓展到带障碍物、三角形网格、从终点往回 DP 等多种变种。


相关推荐
BothSavage7 小时前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法
小林ixn7 小时前
从暴力到KMP:一道题彻底搞懂字符串匹配的前世今生
算法
烬羽8 小时前
字符串算法入门:从反转字符串到回文判断,面试不再慌
算法·面试
先吃饱再说1 天前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰1 天前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术1 天前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六1 天前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize1 天前
初识DFS 与 BFS:递归、队列与图遍历
算法