最小路径和 图解题解
这道题到底在问什么
- 输入
- grid=[[3,1,5,2,4],[2,8,3,6,1],[4,2,7,1,5],[6,3,2,4,2]]
- 输出
- 22
最优解:为什么这么做
一句话答案:LeetCode 64 最小路径和的标准解法是二维动态规划:dp[i][j] 表示从左上角走到 (i,j) 的最小路径和,由于每格只能从上方或左方来,转移是本格数字加 min(dp[i-1][j], dp[i][j-1])。首行首列只有单一来路,先单向累加打底,右下角一格就是答案,时间 O(m×n)、空间可压到 O(n)。
最小路径和这道题在问什么
一个 m×n 的网格,每格有一个非负数字。从左上角 (0,0) 出发,每步只能向右或向下走一格,到达右下角 (m-1,n-1),求沿途数字之和最小是多少。两个隐含条件值得先记下:只能右、下两个方向,意味着路径不会走回头路;格子里的数非负,意味着不存在「绕远反而更省」的怪路。
为什么不能枚举所有路径
暴力思路是回溯出每一条从左上到右下的路径、逐条算和取最小。但路径条数是组合级的:每条路要走 m-1 步向下、n-1 步向右,排列方式随网格增大爆炸式增长,稍大的网格就算不完。
浪费在哪里?海量路径共享着相同的前半段。其实走到任何一个格子 (i,j) 时,前面那段路怎么绕的根本不重要,对后续决策有用的信息只有一个数:到这里的最小花费。把每个格子的这个数记下来复用,指数条路径就坍缩成 m×n 个子问题——这就是无后效性的白话版:到达 (i,j) 之后怎么走,与之前的路线无关。
dp 转移方程为什么是 min 加本格
定义 dp[i][j] 为从起点走到 (i,j) 的最小路径和。任何到达 (i,j) 的路,最后一步只有两种可能:从上方 (i-1,j) 下来,或从左方 (i,j-1) 过来。走上方来路的最优花费是 dp[i-1][j],走左方来路的最优是 dp[i][j-1],二者取小、再加上本格数字,就是 dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])。
为什么这样就是对的?假如最优路径经由上方进入 (i,j),它在 (i-1,j) 之前的部分必然也是到 (i-1,j) 的最优——否则把那段换成更省的,总和还能更小,与最优矛盾。所以只需比较两条来路各自的最优值。注意是取 min 不是相加:一条路只能从一个方向进来,把两个来路加在一起没有物理意义,这是初学者最常写错的一笔。
首行和首列为什么要单独处理
第一行的格子上方没有邻居,只能从左边一路走过来,所以是前缀累加:dp[0][j] = dp[0][j-1] + grid[0][j];第一列同理只能从上面来。如果不单独处理、对不存在的方向也去取 min,要么数组越界,要么取到未初始化的 0 而把答案算小。起点 dp[0][0] 就是 grid[0][0] 本身。打好这一行一列的底,内部格子逐行推过去,右下角 dp[m-1][n-1] 即答案——对示例那个 4×5 网格,结果是 22。
复杂度多少,空间还能怎么省
每个格子恰好算一次、每次 O(1),时间 O(m×n);存整张表空间 O(m×n)。因为 dp[i][j] 只依赖上一行同列和本行左邻,可以用一维数组滚动:dp[j] = grid[i][j] + min(dp[j], dp[j-1]),其中 dp[j] 更新前恰好是上一行的值、dp[j-1] 是本行刚算完的左邻,空间降到 O(n)。若题目允许改输入,甚至能直接在 grid 上原地累加做到 O(1) 额外空间。最后提醒:答案在右下角那格,别返回错位置。
▶ 动画逐步走查(共 34 步)——想跟着动画一帧帧对照就展开
- 3记住这句「本格值 + 上、左较小的」,下面每一格都在套它。
- 4起点 (0,0):还没走任何一步,路径和就是这格自己的数字,dp[0][0] = 3。
- 5第一行 (0,1):上面没有格子,只能从左边那格走过来,左边和 3 加本格 1 = 4。
- 6第一行 (0,2):上面没有格子,只能从左边那格走过来,左边和 4 加本格 5 = 9。
- 7第一行 (0,3):上面没有格子,只能从左边那格走过来,左边和 9 加本格 2 = 11。
- 8第一行 (0,4):上面没有格子,只能从左边那格走过来,左边和 11 加本格 4 = 15。
- 9第一列 (1,0):左边没有格子,只能从上面那格走过来,上面和 3 加本格 2 = 5。
- 10算 (1,1):正上方 dp[0][1]=4(蓝),正左方 dp[1][0]=5(蓝),先挑两条来路里较小的 4。
- 11较小的是 4(走上面更省),再加本格 8 = 12,填进 (1,1)。
- 12算 (1,2):正上方 dp[0][2]=9(蓝),正左方 dp[1][1]=12(蓝),先挑两条来路里较小的 9。
- 13较小的是 9(走上面更省),再加本格 3 = 12,填进 (1,2)。
- 14算 (1,3):正上方 dp[0][3]=11(蓝),正左方 dp[1][2]=12(蓝),先挑两条来路里较小的 11。
- 15较小的是 11(走上面更省),再加本格 6 = 17,填进 (1,3)。
- 16算 (1,4):正上方 dp[0][4]=15(蓝),正左方 dp[1][3]=17(蓝),先挑两条来路里较小的 15。
- 17较小的是 15(走上面更省),再加本格 1 = 16,填进 (1,4)。
- 18第一列 (2,0):左边没有格子,只能从上面那格走过来,上面和 5 加本格 4 = 9。
- 19算 (2,1):正上方 dp[1][1]=12(蓝),正左方 dp[2][0]=9(蓝),先挑两条来路里较小的 9。
- 20较小的是 9(走左边更省),再加本格 2 = 11,填进 (2,1)。
- 21算 (2,2):正上方 dp[1][2]=12(蓝),正左方 dp[2][1]=11(蓝),先挑两条来路里较小的 11。
- 22较小的是 11(走左边更省),再加本格 7 = 18,填进 (2,2)。
- 23算 (2,3):正上方 dp[1][3]=17(蓝),正左方 dp[2][2]=18(蓝),先挑两条来路里较小的 17。
- 24较小的是 17(走上面更省),再加本格 1 = 18,填进 (2,3)。
- 25算 (2,4):正上方 dp[1][4]=16(蓝),正左方 dp[2][3]=18(蓝),先挑两条来路里较小的 16。
- 26较小的是 16(走上面更省),再加本格 5 = 21,填进 (2,4)。
- 27第一列 (3,0):左边没有格子,只能从上面那格走过来,上面和 9 加本格 6 = 15。
- 28算 (3,1):正上方 dp[2][1]=11(蓝),正左方 dp[3][0]=15(蓝),先挑两条来路里较小的 11。
- 29较小的是 11(走上面更省),再加本格 3 = 14,填进 (3,1)。
- 30算 (3,2):正上方 dp[2][2]=18(蓝),正左方 dp[3][1]=14(蓝),先挑两条来路里较小的 14。
- 31较小的是 14(走左边更省),再加本格 2 = 16,填进 (3,2)。
- 32算 (3,3):正上方 dp[2][3]=18(蓝),正左方 dp[3][2]=16(蓝),先挑两条来路里较小的 16。
- 33较小的是 16(走左边更省),再加本格 4 = 20,填进 (3,3)。
- 34算 (3,4):正上方 dp[2][4]=21(蓝),正左方 dp[3][3]=20(蓝),先挑两条来路里较小的 20。
- 35较小的是 20(走左边更省),再加本格 2 = 22,填进 (3,4)。
- 36表填满了。右下角 dp[3][4] = 22,就是从左上走到右下、数字之和最小的那条路。
⚠️ 容易写错的地方
✗ 错:内部格用了加法 dp[i-1][j]+dp[i][j-1]
✓ 对:是 min 不是相加
我们要最小路径,只走其中一条来路,不是两条都走
✗ 错:首行首列忘了单向累加
✓ 对:首行只加左、首列只加上
它们各自只有一个方向能来,对另一边取 min 会越界或取到 0
✗ 错:答案取错格
✓ 对:答案是 dp[m-1][n-1]
终点是右下角那一格
完整代码(Python / C++ / Java)
Python
def minPathSum(grid):
m, n = len(grid), len(grid[0])
dp = [[0]*n for _ in range(m)]
dp[0][0] = grid[0][0]
for j in range(1, n): dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1, m): dp[i][0] = dp[i-1][0] + grid[i][0]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
return dp[m-1][n-1]C++
int minPathSum(vector<vector<int>>& grid){
int m = grid.size(), n = grid[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
dp[0][0] = grid[0][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++) dp[i][0] = dp[i-1][0] + grid[i][0];
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];Java
int minPathSum(int[][] grid){
int m = grid.length, n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][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++) dp[i][0] = dp[i-1][0] + grid[i][0];
for(int i=1;i<m;i++)
for(int j=1;j<n;j++)
dp[i][j] = grid[i][j] + Math.min(dp[i-1][j], dp[i][j-1]);
return dp[m-1][n-1];复杂度
时间
O(m×n)
每格只算一次
空间
O(m×n)
存了整张表(可压成一行 O(n))
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最小路径和 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
只问最小和,能优化到 O(n) 空间吗?+
能。每格只依赖上一行同列 dp[j] 和本行左边 dp[j-1],用一维数组滚动 dp[j] = grid[i][j] + min(dp[j], dp[j-1]) 即可。
如果还要还原出具体走的那条路径呢?+
填表时多记每格是从上还是从左来的(或回填时比较 dp[i-1][j] 与 dp[i][j-1]),从终点反推回起点即得路径。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 最小路径和 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。