给定一个包含非负整数的 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.length`
- `n == grid[i].length`
- `1 <= m, n <= 200`
- `0 <= grid[i][j] <= 200`
🏠 生活案例:打车避堵指南
想象你在一个棋盘格一样的城市里(比如纽约曼哈顿),你要从左上角的 A 点去往右下角的 B 点。
- 每个格子里都有一个数字,代表通过这个街口需要等待的红灯时间。
- 交规很严格:你只能向右走或者向下走,不能回头。
你的目标是:找一条路,让你一路上等的红灯总时间最短。
💻 代码实现与生活化注释
这段代码采用了“原地记账”的动态规划策略。它直接在原地图(grid)上修改,把每个格子原本的“单次等待时间”更新为“到达该格子的累计最短时间”。
JavaScript
/**
* @param {number[][]} grid
* @return {number}
*/
var minPathSum = function(grid) {
let m = grid.length;
let n = grid[0].length;
// 1. 处理最左边的一列(只能一路向下)
// 到达这里别无选择,只能把上面格子的累计时间加过来
for(let i = 1; i < m; i++){
grid[i][0] = grid[i][0] + grid[i - 1][0];
}
// 2. 处理最上面的一行(只能一路向右)
// 同样没得选,只能把左边格子的累计时间加过来
for(let j = 1; j < n; j++){
grid[0][j] = grid[0][j] + grid[0][j - 1];
}
// 3. 处理剩下的“腹地”格子
// 每个格子都有两个来源:左边过来的,或者上面过来的
for(let i = 1; i < m; i++){
for(let j = 1; j < n; j++){
// 核心逻辑:我要选一个更短的路!
// 比较 [上面格子] 和 [左边格子] 的累计时间,选小的那个
// 加上当前格子的红灯时间,就是到达当前位置的最优方案
grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
}
}
// 4. 最终右下角的那个数字,就是全局最优的“避堵”总时长
return grid[m - 1][n - 1];
};
🧩 图解推演过程
-
第一步(初始化边缘) :
你会发现第一行和第一列的数值是逐渐累加的。因为在边缘上,你没有选择余地(比如在第一行,你只能从左边来)。
-
第二步(填补中间) :
当你走到中间的格子(比如坐标 1,1)时,计算机会想:“我是从上面降落比较快,还是从左边走过来比较快?”它记下那个较小的值,就像你在导航里看哪条路是绿色的。
💡 为什么这段代码写得好?
- 时间效率 :只需要把整个地图扫描一遍,速度极快。
- 空间优化 :注意看,代码并没有开辟一个新的
dp数组,而是直接在输入的grid数组上操作。这在算法面试中是一个加分点,因为它不消耗额外内存(原地修改)。