【力扣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 等多种变种。


相关推荐
地平线开发者6 小时前
【模型轻量化专题】衡量模型轻量性的指标
算法
学习星球9 小时前
OFDM技术精讲:正交性推导、循环前缀原理与80行Python链路仿真(附实测数据)
算法·面试·前端框架
alphaTao15 小时前
LeetCode 每日一题 2026/8/24-2026/8/30
python·算法·leetcode
Interview Aid11216 小时前
TikTok OA 四题分享|半小时内 AC,题目基本都是实现题
java·开发语言·算法·面试·职场和发展
顶点多余1 天前
那些在算法中适合巩固的知识点---1
java·前端·算法
罗西的思考1 天前
【Agentic RL / 强化学习框架】Molt 设计解读
人工智能·算法·机器学习
hahaha60161 天前
HLS高层次综合设计技巧--C++类和模板
图像处理·人工智能·算法·计算机视觉
多弗朗皮卡丘1 天前
算法详解4:买卖股票的最佳时机系列(上)
算法
维克兜率天1 天前
【维克】动量指标家族:RSI、ROC、CCI、Momentum全面解析
python·算法
AI情绪识别开源1 天前
检信 AI 智能推广平台(代号:JX-Promote)
人工智能·算法·erlang