LeetCode 64:最小路径和
1. 题目核心
给定一个 m × n 的非负整数网格 grid,从左上角 (0,0) 出发到右下角 (m-1,n-1),每次只能:
向右
向下
要求找到一条路径,使经过的所有数字之和最小,并返回这个最小路径和。
例如:
1 3 1
1 5 1
4 2 1
最优路径:
1 → 3 → 1
↓
1
↓
1
路径和:
1 + 3 + 1 + 1 + 1 = 7
2. 思路一:DFS / 枚举所有路径
从 (0,0) 开始,每个位置尝试:
向右
向下
一直走到右下角,计算每条路径的数字总和,最后取最小值。
这种方法逻辑直观,但会产生大量重复搜索,例如同一个位置 (i,j) 可以通过很多不同路线到达,而到达之后的后续路线会被重复计算,因此效率较低。
3. 思路二:动态规划 DP
3.1 DP 状态定义
定义:
dp[i][j]
表示:
从左上角
(0,0)走到(i,j)时,可以得到的最小路径和。
注意这题和上一题"不同路径 II"很像,但含义完全不同:
不同路径:
dp[i][j] = 到达当前位置有多少条路径
最小路径和:
dp[i][j] = 到达当前位置的最小数字总和
3.2 为什么只看上面和左边
机器人只能:
向右
向下
所以想走到 (i,j),最后一步只能来自:
上面:(i-1,j)
左边:(i,j-1)
假设:
从上面到达时最小路径和 = 10
从左边到达时最小路径和 = 7
当前格子的值 = 3
显然应该选择左边:
7 + 3 = 10
而不是:
10 + 3 = 13
所以:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
3.3 为什么这里用 min,上一题却用 +
上一题"不同路径"求的是:
一共有多少种方案
因此:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
这题求的是:
所有方案中哪一个代价最小
所以:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
可以记:
求方案总数 → +
求最大值 → max
求最小值 → min
4. 初始化
4.1 起点
到达起点 (0,0) 什么地方都不用走,因此路径和就是当前格子的值:
dp[0][0] = grid[0][0];
4.2 第一列
第一列的格子只能从上面走下来:
(0,0)
↓
(1,0)
↓
(2,0)
所以:
dp[i][0] = dp[i - 1][0] + grid[i][0];
例如:
grid:
1
1
4
那么:
dp:
1
2
6
4.3 第一行
第一行只能一直向右:
(0,0) → (0,1) → (0,2)
所以:
dp[0][j] = dp[0][j - 1] + grid[0][j];
5. 示例完整推导
对于:
grid =
1 3 1
1 5 1
4 2 1
初始化起点:
dp =
1 ? ?
? ? ?
? ? ?
初始化第一行:
dp[0][0] = 1
dp[0][1] = 1 + 3 = 4
dp[0][2] = 4 + 1 = 5
得到:
1 4 5
? ? ?
? ? ?
初始化第一列:
dp[1][0] = 1 + 1 = 2
dp[2][0] = 2 + 4 = 6
得到:
1 4 5
2 ? ?
6 ? ?
计算 (1,1):
min(上面4, 左边2) + 5
= 2 + 5
= 7
计算 (1,2):
min(上面5, 左边7) + 1
= 5 + 1
= 6
计算 (2,1):
min(上面7, 左边6) + 2
= 6 + 2
= 8
计算 (2,2):
min(上面6, 左边8) + 1
= 6 + 1
= 7
最终 DP:
1 4 5
2 7 6
6 8 7
因此:
答案 = dp[2][2] = 7
6. C++:二维 DP
你截图中的思路和代码就是这个标准解法。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
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] =
min(dp[i - 1][j], dp[i][j - 1])
+ grid[i][j];
}
}
return dp[m - 1][n - 1];
}
};
7. Java:二维 DP
Java 使用的是完全相同的动态规划思路。
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
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] =
Math.min(dp[i - 1][j], dp[i][j - 1])
+ grid[i][j];
}
}
return dp[m - 1][n - 1];
}
}
8. Java 语法解释
8.1 二维数组
int[][] grid
对应 C++:
vector<vector<int>> grid
8.2 获取行数和列数
Java:
int m = grid.length;
int n = grid[0].length;
C++:
int m = grid.size();
int n = grid[0].size();
Java 数组使用 .length,没有括号。
8.3 创建二维数组
int[][] dp = new int[m][n];
表示创建一个 m × n 的二维整数数组,默认所有元素初始化为 0。
8.4 Math.min
Java:
Math.min(a, b)
对应 C++:
min(a, b)
所以:
dp[i][j] =
Math.min(dp[i - 1][j], dp[i][j - 1])
+ grid[i][j];
就是:
选择从上面和左边过来时路径和更小的那一个,再加当前格子的值。
9. Python:二维 DP
Python 同样使用相同的 DP。
class Solution: def minPathSum(self, grid): m = len(grid) n = len(grid[0]) dp = [[0] * n for _ in range(m)] dp[0][0] = grid[0][0] # 初始化第一列 for i in range(1, m): dp[i][0] = dp[i - 1][0] + grid[i][0] # 初始化第一行 for j in range(1, n): dp[0][j] = dp[0][j - 1] + grid[0][j] # 状态转移 for i in range(1, m): for j in range(1, n): dp[i][j] = ( min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j] ) return dp[m - 1][n - 1]
10. Python 语法解释
10.1 获取二维数组大小
m = len(grid)n = len(grid[0])
len(grid) 是行数,len(grid[0]) 是列数。
10.2 创建二维数组
dp = [[0] * n for _ in range(m)]
例如:
m = 3
n = 3
得到:
[
[0,0,0],
[0,0,0],
[0,0,0]
]
10.3 Python 的 min
min(a, b)
直接得到两个数中更小的一个。
10.4 range(1, m)
for i in range(1, m):
表示:
i = 1,2,...,m-1
对应 C++:
for (int i = 1; i < m; ++i)
11. 空间优化:一维 DP
因为:
dp[i][j]
只依赖:
上面 dp[i-1][j]
左边 dp[i][j-1]
所以可以只保存一行。
使用:
dp[j]
表示当前处理到第 j 列时的最小路径和。
更新前:
dp[j] = 上面那个格子的最小路径和
而:
dp[j-1] = 当前行左边格子的最小路径和
所以:
dp[j] = min(dp[j], dp[j - 1]) + grid[i][j];
空间复杂度可以从:
O(m × n)
降低到:
O(n)
12. C++:一维空间优化
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
vector<int> dp(n, 0);
dp[0] = grid[0][0];
// 初始化第一行
for (int j = 1; j < n; ++j) {
dp[j] = dp[j - 1] + grid[0][j];
}
for (int i = 1; i < m; ++i) {
// 当前行第一列只能从上面来
dp[0] += grid[i][0];
for (int j = 1; j < n; ++j) {
dp[j] =
min(dp[j], dp[j - 1])
+ grid[i][j];
}
}
return dp[n - 1];
}
};
13. 复杂度
二维 DP:
时间复杂度:O(m × n)
空间复杂度:O(m × n)
一维 DP:
时间复杂度:O(m × n)
空间复杂度:O(n)
14. 与 LeetCode 63 的联系
两道题几乎是同一个网格 DP 模板。
LeetCode 63:不同路径 II
求:
有多少条路线
所以:
dp[i][j] =
dp[i - 1][j] + dp[i][j - 1];
LeetCode 64:最小路径和
求:
哪条路线数字之和最小
所以:
dp[i][j] =
min(dp[i - 1][j], dp[i][j - 1])
+ grid[i][j];
它们共同的地方都是:
当前位置只能从:
上面
左边
过来
区别只是题目问的东西不同:
统计所有方案 → 相加
选最优方案 → min/max
15. 核心记忆
DP 定义
dp[i][j]
=
从左上角走到 (i,j)
所能得到的最小路径和
初始化
dp[0][0] = grid[0][0];
dp[i][0] =
dp[i - 1][0] + grid[i][0];
dp[0][j] =
dp[0][j - 1] + grid[0][j];
状态转移
dp[i][j] =
min(dp[i - 1][j], dp[i][j - 1])
+ grid[i][j];
最后只记一句:
到当前格子的最小路径和 = 上面和左边两个来源中更小的路径和 + 当前格子的数字。