LeetCode 第64题:最小路径和
题目描述
给定一个包含非负整数的 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.lengthn == grid[i].length1 <= m, n <= 2000 <= grid[i][j] <= 100
解题思路
动态规划
这是一个典型的动态规划问题,每个位置的最小路径和取决于其上方和左方的最小路径和。
关键点:
- 状态定义:dp[i][j]表示到达位置(i,j)的最小路径和
- 状态转移方程:dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
- 边界条件:第一行和第一列的路径和是累加的
- 可以直接在原数组上修改,节省空间
具体步骤:
- 初始化第一行和第一列
- 遍历网格,应用状态转移方程
- 返回右下角的值
- 注意处理边界情况
图解思路
算法步骤分析表
| 步骤 | 操作 | 状态 | 说明 |
|---|---|---|---|
| 初始 | 原始 | [[1,3,1],[1,5,1],[4,2,1]] | 原始网格 |
| 第1步 | 初始化 | [[1,4,5],[1,5,1],[4,2,1]] | 处理第一行 |
| 第2步 | 计算 | [[1,4,5],[2,7,6],[4,2,1]] | 处理第二行 |
| 最终 | 完成 | [[1,4,5],[2,7,6],[6,8,7]] | 得到结果 |
状态/情况分析表
| 情况 | 输入 | 输出 | 说明 |
|---|---|---|---|
| 1×1网格 | [[1]] | 1 | 最简单情况 |
| 单行/列 | [[1,2,3]] | 6 | 只能一直向右 |
| 正方形 | [[1,2],[3,4]] | 7 | 标准情况 |
代码实现
C# 实现
public class Solution {
public int MinPathSum(int[][] grid) {
if (grid == null || grid.Length == 0) return 0;
int m = grid.Length;
int n = grid[0].Length;
// 初始化第一行
for (int j = 1; j < n; j++) {
grid[0][j] += grid[0][j-1];
}
// 初始化第一列
for (int i = 1; i < m; i++) {
grid[i][0] += grid[i-1][0];
}
// 动态规划填充其他位置
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
grid[i][j] += Math.Min(grid[i-1][j], grid[i][j-1]);
}
}
return grid[m-1][n-1];
}
}
Python 实现
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
# 初始化第一行
for j in range(1, n):
grid[0][j] += grid[0][j-1]
# 初始化第一列
for i in range(1, m):
grid[i][0] += grid[i-1][0]
# 动态规划填充其他位置
for i in range(1, m):
for j in range(1, n):
grid[i][j] += min(grid[i-1][j], grid[i][j-1])
return grid[m-1][n-1]
C++ 实现
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
if (grid.empty() || grid[0].empty()) return 0;
int m = grid.size();
int n = grid[0].size();
// 初始化第一行
for (int j = 1; j < n; j++) {
grid[0][j] += grid[0][j-1];
}
// 初始化第一列
for (int i = 1; i < m; i++) {
grid[i][0] += grid[i-1][0];
}
// 动态规划填充其他位置
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];
}
};
执行结果
- 执行用时:92 ms
- 内存消耗:39.5 MB
代码亮点
- 🎯 原地修改数组,节省空间
- 💡 巧妙处理边界情况
- 🔍 代码逻辑清晰简洁
- 🎨 无需额外空间
常见错误分析
- 🚫 没有处理空数组的情况
- 🚫 边界条件初始化错误
- 🚫 状态转移方程使用错误
- 🚫 返回值位置错误
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| DFS递归 | O(2^(m+n)) | O(m+n) | 直观易懂 | 超时 |
| 二维DP | O(mn) | O(mn) | 容易理解 | 空间消耗大 |
| 一维DP | O(mn) | O(n) | 空间优化 | 不够直观 |
| 原地修改 | O(mn) | O(1) | 最优解法 | 修改输入 |