(LeetCode-Hot100)64. 最小路径和

64. 最小路径和

🔗 LeetCode 题目链接

❓ 问题简介

给定一个包含非负整数的 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

💡 解题思路

这是一个典型的**动态规划(Dynamic Programming)**问题。我们目标是从左上角 (0, 0) 到右下角 (m-1, n-1) 找出路径和最小的路径。

✅ 思路一:二维 DP(推荐)

步骤分解:
  1. 定义状态

    dp[i][j] 表示从 (0, 0)(i, j) 的最小路径和。

  2. 状态转移方程

    因为只能从上方或左方过来:

    复制代码
    dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
  3. 边界条件

    • 第一行只能从左边来:dp[0][j] = dp[0][j-1] + grid[0][j]
    • 第一列只能从上面来:dp[i][0] = dp[i-1][0] + grid[i][0]
  4. 初始化
    dp[0][0] = grid[0][0]

  5. 结果

    返回 dp[m-1][n-1]


✅ 思路二:空间优化的一维 DP

观察发现,计算当前行时只依赖上一行和当前行左侧的值,因此可以用一维数组优化空间。

  • 使用长度为 n 的数组 dp
  • 更新顺序:从左到右
  • 转移方程:
    • 对于第一行:dp[j] = dp[j-1] + grid[0][j]
    • 对于其他行:
      • dp[0] += grid[i][0](第一列)
      • dp[j] = grid[i][j] + min(dp[j], dp[j-1])

❌ 其他不推荐方法

  • DFS / BFS 暴力搜索:时间复杂度过高(指数级),会超时。
  • 记忆化递归:本质上等价于 DP,但常数较大,不如迭代清晰。

💻 代码实现

java 复制代码
// 方法一:二维 DP
class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int 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];
    }
}

// 方法二:一维 DP(空间优化)
class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[] dp = new int[n];
        
        dp[0] = grid[0][0];
        
        // 初始化第一行
        for (int j = 1; j < n; j++) {
            dp[j] = dp[j - 1] + grid[0][j];
        }
        
        // 从第二行开始更新
        for (int i = 1; i < m; i++) {
            dp[0] += grid[i][0]; // 第一列只能从上边来
            for (int j = 1; j < n; j++) {
                dp[j] = grid[i][j] + Math.min(dp[j], dp[j - 1]);
            }
        }
        
        return dp[n - 1];
    }
}
go 复制代码
// 方法一:二维 DP
func minPathSum(grid [][]int) int {
    m, n := len(grid), len(grid[0])
    dp := make([][]int, m)
    for i := range dp {
        dp[i] = make([]int, n)
    }
    
    dp[0][0] = grid[0][0]
    
    // 初始化第一行
    for j := 1; j < n; j++ {
        dp[0][j] = dp[0][j-1] + grid[0][j]
    }
    
    // 初始化第一列
    for i := 1; i < m; i++ {
        dp[i][0] = dp[i-1][0] + grid[i][0]
    }
    
    // 填充其余位置
    for i := 1; i < m; i++ {
        for 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]
}

// 方法二:一维 DP(空间优化)
func minPathSum(grid [][]int) int {
    m, n := len(grid), len(grid[0])
    dp := make([]int, n)
    
    dp[0] = grid[0][0]
    
    // 初始化第一行
    for j := 1; j < n; j++ {
        dp[j] = dp[j-1] + grid[0][j]
    }
    
    // 从第二行开始更新
    for i := 1; i < m; i++ {
        dp[0] += grid[i][0] // 第一列只能从上边来
        for j := 1; j < n; j++ {
            dp[j] = grid[i][j] + min(dp[j], dp[j-1])
        }
    }
    
    return dp[n-1]
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

💡 注意:Go 语言中需要手动实现 min 函数(Go 1.21+ 可用 slices.Min,但此处为通用性自定义)。


🧪 示例演示

grid = [[1,3,1],[1,5,1],[4,2,1]] 为例:

二维 DP 表构建过程:

0 1 2
0 1 4 5
1 2 7 6
2 6 8 7
  • dp[0][0] = 1
  • dp[0][1] = 1+3=4, dp[0][2]=4+1=5
  • dp[1][0] = 1+1=2
  • dp[1][1] = 5 + min(2,4) = 7
  • dp[1][2] = 1 + min(7,5) = 6
  • dp[2][0] = 2+4=6
  • dp[2][1] = 2 + min(6,7) = 8
  • dp[2][2] = 1 + min(8,6) = 7

最终答案:7


✅ 答案有效性证明

我们可以用数学归纳法证明 DP 的正确性:

  • 基础情况dp[0][0] = grid[0][0] 显然正确。
  • 归纳假设 :假设对于所有 (i', j') 满足 i' < ij' < jdp[i'][j'] 已是最小路径和。
  • 归纳步骤 :到达 (i, j) 只能从 (i-1, j)(i, j-1) 来,根据归纳假设,这两个位置的值已是最优,因此取较小者加上 grid[i][j] 即为 (i, j) 的最优解。

因此,DP 状态转移正确,最终结果有效。


📊 复杂度分析

方法 时间复杂度 空间复杂度
二维 DP O(m × n) O(m × n)
一维 DP(优化) O(m × n) O(n)
  • 时间 :每个格子访问一次,共 m × n 次。
  • 空间:二维 DP 需要额外二维数组;一维 DP 只需一行空间,可原地优化(若允许修改原数组,甚至可 O(1) 空间)。

💡 实际面试中,建议先写二维 DP 清晰表达思路,再提出空间优化方案。


🔚 问题总结

  • 核心思想:动态规划,利用子问题最优解构造全局最优解。
  • 关键观察:只能向右或向下 → 当前状态仅依赖上方和左方。
  • 优化方向:从二维 DP 到一维 DP,空间从 O(mn) 降至 O(n)。
  • 适用场景:网格路径、最小/最大代价类问题(如不同路径、三角形最小路径和等)。

📌 延伸思考

  • 如果允许向四个方向移动?→ 需用 Dijkstra(带权最短路)。
  • 如果有障碍物?→ 在 DP 中跳过障碍位置即可(如 LeetCode 63)。

掌握此类 DP 模板,可快速解决大量网格路径优化问题!

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
汉克老师1 小时前
GESP2024年6月认证C++二级( 第三部分编程题(1) 平方之和)
c++·算法·预处理·完全平方数·循环结构·gesp二级·gesp2级
云深处@1 小时前
【题】每日一题
算法
学习是生活的调味剂2 小时前
nacos原理之服务注册浅析
java·开发语言·nacos·注册中心
智者知已应修善业2 小时前
【排列顺序判断是否一次交换能得到升序】2025-1-28
c语言·c++·经验分享·笔记·算法
没有bug.的程序员2 小时前
Arthas 深度进阶:线上问题非侵入式诊断内核、方法级监控与线程阻塞排查实战指南
java·arthas·线上问题·非侵入式·方法级监控·线程阻塞
亓才孓2 小时前
[Mybatis]Mybatis框架
java·数据库·mybatis
跟Tom学编程—一对一编程辅导2 小时前
基于 Java 的 SSM 架构电子商城项目毕业设计课题选型指导文档|名企高级开发工程师全程一对一指导(含详细文档+源码+部署)
java·架构·毕业设计·课程设计
编程小风筝3 小时前
编写java代码如何写文档注释?
java·开发语言
yzs873 小时前
OLAP数据库HashJoin性能优化揭秘
数据库·算法·性能优化·哈希算法