【中等】64. 最小路径和

0 阅读3分钟

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明: 每次只能向下或者向右移动一步。

示例 1:

输入: grid = [[1,3,1],[1,5,1],[4,2,1]]
输出: 7
解释: 因为路径 13111 的总和最小。

示例 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. 第一步(初始化边缘)

    你会发现第一行和第一列的数值是逐渐累加的。因为在边缘上,你没有选择余地(比如在第一行,你只能从左边来)。

  2. 第二步(填补中间)

    当你走到中间的格子(比如坐标 1,1)时,计算机会想:“我是从上面降落比较快,还是从左边走过来比较快?”它记下那个较小的值,就像你在导航里看哪条路是绿色的。


💡 为什么这段代码写得好?

  • 时间效率 O(m×n)O(m \times n) :只需要把整个地图扫描一遍,速度极快。
  • 空间优化 O(1)O(1) :注意看,代码并没有开辟一个新的 dp 数组,而是直接在输入的 grid 数组上操作。这在算法面试中是一个加分点,因为它不消耗额外内存(原地修改)。