【每日算法】LeetCode 64. 最小路径和(多维动态规划)

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 64. 最小路径和

1. 题目描述

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

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

示例 1

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

示例 2

复制代码
输入:grid = [[1,2,3],[4,5,6]]
输出:12
解释:路径 1→2→3→6 的总和为 12。

提示

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 200
  • 0 <= grid[i][j] <= 100

2. 问题分析

这个问题是典型的二维网格路径问题,具有以下特点:

  1. 只能向右或向下移动:这意味着到达每个点的路径只能来自上方或左方
  2. 求最小路径和:需要从所有可能路径中找到和最小的那条
  3. 非负整数网格:这个条件保证了贪心算法可能可行,但实际需要动态规划

前端视角:可以将其类比为在一个二维表格(如Excel)中,从左上角单元格移动到右下角单元格,每个单元格有成本,需要找到最低成本的路径。

3. 解题思路

3.1 思路一:暴力递归(回溯法)

通过递归尝试所有可能的路径,计算每条路径的和,取最小值。时间复杂度为指数级,不推荐。

3.2 思路二:记忆化搜索

在递归基础上添加记忆化,避免重复计算子问题。时间复杂度O(m×n),空间复杂度O(m×n)。

3.3 思路三:动态规划(最优解)

使用DP表存储到达每个位置的最小路径和,自底向上计算。时间复杂度O(m×n),空间复杂度可优化到O(n)或O(1)。

3.4 思路四:在原数组上修改(原地DP)

直接修改原数组,将空间复杂度优化到O(1)。这是最优解之一,适合允许修改原数组的场景。

最优解:动态规划,时间复杂度O(m×n),空间复杂度可优化到O(min(m,n))或O(1)。

4. 代码实现

4.1 基础动态规划(二维DP表)

javascript 复制代码
/**
 * 最小路径和 - 基础动态规划(二维DP表)
 * 时间复杂度:O(m×n)
 * 空间复杂度:O(m×n)
 * 
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    const m = grid.length;      // 行数
    const n = grid[0].length;   // 列数
    
    // 创建DP表,dp[i][j]表示从(0,0)到(i,j)的最小路径和
    const dp = Array.from({ length: m }, () => new Array(n).fill(0));
    
    // 初始化起点
    dp[0][0] = grid[0][0];
    
    // 初始化第一列:只能从上方下来
    for (let i = 1; i < m; i++) {
        dp[i][0] = dp[i-1][0] + grid[i][0];
    }
    
    // 初始化第一行:只能从左方过来
    for (let j = 1; j < n; j++) {
        dp[0][j] = dp[0][j-1] + grid[0][j];
    }
    
    // 填充DP表:每个位置的最小路径和 = 当前值 + min(上方值, 左方值)
    for (let i = 1; i < m; i++) {
        for (let 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];
};

// 示例演示
const grid = [
    [1, 3, 1],
    [1, 5, 1],
    [4, 2, 1]
];

console.log(minPathSum(grid)); // 输出: 7

// 步骤分解(DP表填充过程):
// 初始:        | 计算后:
// [1, 3, 1]    | [1, 4, 5]
// [1, 5, 1]    | [2, 7, 6]
// [4, 2, 1]    | [6, 8, 7] ← 右下角7就是答案

4.2 空间优化动态规划(一维DP)

javascript 复制代码
/**
 * 最小路径和 - 空间优化动态规划(一维DP)
 * 时间复杂度:O(m×n)
 * 空间复杂度:O(n) - 只存储一行数据
 * 
 * 前端应用场景:处理大型表格数据时减少内存占用
 * 
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    const m = grid.length;
    const n = grid[0].length;
    
    // 使用一维数组存储当前行的DP值
    const dp = new Array(n).fill(0);
    
    // 初始化第一行
    dp[0] = grid[0][0];
    for (let j = 1; j < n; j++) {
        dp[j] = dp[j-1] + grid[0][j];
    }
    
    // 逐行计算
    for (let i = 1; i < m; i++) {
        // 更新当前行的第一个元素
        dp[0] = dp[0] + grid[i][0];
        
        for (let j = 1; j < n; j++) {
            // dp[j] 当前是上一行的值(相当于dp[i-1][j])
            // dp[j-1] 是当前行已计算的值(相当于dp[i][j-1])
            dp[j] = grid[i][j] + Math.min(dp[j], dp[j-1]);
        }
    }
    
    return dp[n-1];
};

// 空间优化原理:
// 我们只关心当前行和上一行的数据,所以可以用一行来滚动更新
// dp[j]在更新前代表上一行的结果,更新后代表当前行的结果

4.3 原地修改(O(1)空间)

javascript 复制代码
/**
 * 最小路径和 - 原地修改(O(1)空间)
 * 时间复杂度:O(m×n)
 * 空间复杂度:O(1) - 直接在原数组上修改
 * 
 * 适用场景:可以修改输入数组,且需要极致空间优化
 * 
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    const m = grid.length;
    const n = grid[0].length;
    
    // 直接修改原数组为DP表
    // 初始化第一列
    for (let i = 1; i < m; i++) {
        grid[i][0] += grid[i-1][0];
    }
    
    // 初始化第一行
    for (let j = 1; j < n; j++) {
        grid[0][j] += grid[0][j-1];
    }
    
    // 动态规划填充
    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]);
        }
    }
    
    return grid[m-1][n-1];
};

4.4 记忆化搜索(递归+缓存)

javascript 复制代码
/**
 * 最小路径和 - 记忆化搜索
 * 时间复杂度:O(m×n) - 每个子问题计算一次
 * 空间复杂度:O(m×n) - 递归栈和记忆表
 * 
 * 适合理解动态规划思想的递归本质
 * 
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    const m = grid.length;
    const n = grid[0].length;
    
    // 创建记忆表,初始化为-1表示未计算
    const memo = Array.from({ length: m }, () => new Array(n).fill(-1));
    
    /**
     * 递归函数:计算从(i,j)到右下角的最小路径和
     * @param {number} i - 当前行
     * @param {number} j - 当前列
     * @return {number}
     */
    const dfs = (i, j) => {
        // 越界检查
        if (i >= m || j >= n) return Infinity;
        
        // 到达终点
        if (i === m - 1 && j === n - 1) return grid[i][j];
        
        // 如果已经计算过,直接返回
        if (memo[i][j] !== -1) return memo[i][j];
        
        // 计算向下和向右的路径和
        const down = dfs(i + 1, j);
        const right = dfs(i, j + 1);
        
        // 选择较小的路径,并加上当前值
        memo[i][j] = grid[i][j] + Math.min(down, right);
        
        return memo[i][j];
    };
    
    return dfs(0, 0);
};

5. 复杂度与优缺点对比

方法 时间复杂度 空间复杂度 优点 缺点 适用场景
基础DP(二维) O(m×n) O(m×n) 逻辑清晰,易于理解 空间占用较大 教学、小规模数据
空间优化DP(一维) O(m×n) O(n) 空间效率高,逻辑清晰 代码稍复杂 大规模数据,内存敏感
原地修改DP O(m×n) O(1) 极致空间优化 修改原数据 可修改输入,内存极限优化
记忆化搜索 O(m×n) O(m×n) 递归思路直观 递归栈可能溢出 理解DP递归本质

6. 总结与拓展

6.1 动态规划模板

javascript 复制代码
/**
 * 二维网格路径问题通用模板
 * 适用条件:只能向右/向下移动,求最优路径
 */
function gridDP(grid) {
    const m = grid.length, n = grid[0].length;
    
    // 1. 创建DP表
    const dp = new Array(m).fill(0).map(() => new Array(n).fill(0));
    
    // 2. 初始化边界
    dp[0][0] = grid[0][0];
    for (let i = 1; i < m; i++) dp[i][0] = dp[i-1][0] + grid[i][0];
    for (let j = 1; j < n; j++) dp[0][j] = dp[0][j-1] + grid[0][j];
    
    // 3. 状态转移
    for (let i = 1; i < m; i++) {
        for (let j = 1; j < n; j++) {
            dp[i][j] = grid[i][j] + Math.min(dp[i-1][j], dp[i][j-1]);
        }
    }
    
    // 4. 返回结果
    return dp[m-1][n-1];
}

6.2 前端应用场景

  1. 资源加载优化:类似于CDN节点选择最优路径
  2. UI渲染优化:组件树渲染成本最小化
  3. 游戏开发:地图寻路算法(网格游戏)
  4. 数据可视化:热力图路径分析

6.3 类似题目推荐

  1. LeetCode 62. 不同路径 - 计算路径数量
  2. LeetCode 63. 不同路径 II - 带有障碍物的版本
  3. LeetCode 120. 三角形最小路径和 - 三角形结构的最小路径
  4. LeetCode 174. 地下城游戏 - 逆向动态规划
  5. LeetCode 221. 最大正方形 - 二维DP求最大面积
  6. LeetCode 931. 下降路径最小和 - 类似问题,方向不同
相关推荐
Salt_07282 小时前
DAY44 简单 CNN
python·深度学习·神经网络·算法·机器学习·计算机视觉·cnn
货拉拉技术2 小时前
AI拍货选车,开启拉货新体验
算法
MobotStone2 小时前
一夜蒸发1000亿美元后,Google用什么夺回AI王座
算法
Wang201220133 小时前
RNN和LSTM对比
人工智能·算法·架构
xueyongfu3 小时前
从Diffusion到VLA pi0(π0)
人工智能·算法·stable diffusion
永远睡不够的入3 小时前
快排(非递归)和归并的实现
数据结构·算法·深度优先
cheems95273 小时前
二叉树深搜算法练习(一)
数据结构·算法
sin_hielo3 小时前
leetcode 3074
数据结构·算法·leetcode
Yzzz-F3 小时前
算法竞赛进阶指南 动态规划 背包
算法·动态规划