LeetCode 64:最小路径和

LeetCode 64:最小路径和

1. 题目核心

给定一个 m × n 的非负整数网格 grid,从左上角 (0,0) 出发到右下角 (m-1,n-1),每次只能:

复制代码
向右
向下

要求找到一条路径,使经过的所有数字之和最小,并返回这个最小路径和。

例如:

复制代码
1 3 1
1 5 1
4 2 1

最优路径:

复制代码
1 → 3 → 1
        ↓
        1
        ↓
        1

路径和:

复制代码
1 + 3 + 1 + 1 + 1 = 7

2. 思路一:DFS / 枚举所有路径

从 (0,0) 开始,每个位置尝试:

复制代码
向右
向下

一直走到右下角,计算每条路径的数字总和,最后取最小值。

这种方法逻辑直观,但会产生大量重复搜索,例如同一个位置 (i,j) 可以通过很多不同路线到达,而到达之后的后续路线会被重复计算,因此效率较低。

3. 思路二:动态规划 DP

3.1 DP 状态定义

定义:

复制代码
dp[i][j]

表示:

从左上角 (0,0) 走到 (i,j) 时,可以得到的最小路径和。

注意这题和上一题"不同路径 II"很像,但含义完全不同:

复制代码
不同路径:
dp[i][j] = 到达当前位置有多少条路径

最小路径和:
dp[i][j] = 到达当前位置的最小数字总和

3.2 为什么只看上面和左边

机器人只能:

复制代码
向右
向下

所以想走到 (i,j),最后一步只能来自:

复制代码
上面:(i-1,j)
左边:(i,j-1)

假设:

复制代码
从上面到达时最小路径和 = 10
从左边到达时最小路径和 = 7
当前格子的值 = 3

显然应该选择左边:

复制代码
7 + 3 = 10

而不是:

复制代码
10 + 3 = 13

所以:

复制代码
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];

3.3 为什么这里用 min,上一题却用 +

上一题"不同路径"求的是:

复制代码
一共有多少种方案

因此:

复制代码
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];

这题求的是:

复制代码
所有方案中哪一个代价最小

所以:

复制代码
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];

可以记:

复制代码
求方案总数 → +
求最大值   → max
求最小值   → min

4. 初始化

4.1 起点

到达起点 (0,0) 什么地方都不用走,因此路径和就是当前格子的值:

复制代码
dp[0][0] = grid[0][0];

4.2 第一列

第一列的格子只能从上面走下来:

复制代码
(0,0)
  ↓
(1,0)
  ↓
(2,0)

所以:

复制代码
dp[i][0] = dp[i - 1][0] + grid[i][0];

例如:

复制代码
grid:
1
1
4

那么:

复制代码
dp:
1
2
6

4.3 第一行

第一行只能一直向右:

复制代码
(0,0) → (0,1) → (0,2)

所以:

复制代码
dp[0][j] = dp[0][j - 1] + grid[0][j];

5. 示例完整推导

对于:

复制代码
grid =
1 3 1
1 5 1
4 2 1

初始化起点:

复制代码
dp =
1 ? ?
? ? ?
? ? ?

初始化第一行:

复制代码
dp[0][0] = 1
dp[0][1] = 1 + 3 = 4
dp[0][2] = 4 + 1 = 5

得到:

复制代码
1 4 5
? ? ?
? ? ?

初始化第一列:

复制代码
dp[1][0] = 1 + 1 = 2
dp[2][0] = 2 + 4 = 6

得到:

复制代码
1 4 5
2 ? ?
6 ? ?

计算 (1,1):

复制代码
min(上面4, 左边2) + 5
= 2 + 5
= 7

计算 (1,2):

复制代码
min(上面5, 左边7) + 1
= 5 + 1
= 6

计算 (2,1):

复制代码
min(上面7, 左边6) + 2
= 6 + 2
= 8

计算 (2,2):

复制代码
min(上面6, 左边8) + 1
= 6 + 1
= 7

最终 DP:

复制代码
1 4 5
2 7 6
6 8 7

因此:

复制代码
答案 = dp[2][2] = 7

6. C++:二维 DP

你截图中的思路和代码就是这个标准解法。

复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();

        vector<vector<int>> dp(m, vector<int>(n, 0));

        dp[0][0] = grid[0][0];

        // 初始化第一列
        for (int i = 1; i < m; ++i) {
            dp[i][0] = dp[i - 1][0] + grid[i][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) {
            for (int j = 1; j < n; ++j) {
                dp[i][j] =
                    min(dp[i - 1][j], dp[i][j - 1])
                    + grid[i][j];
            }
        }

        return dp[m - 1][n - 1];
    }
};

7. Java:二维 DP

Java 使用的是完全相同的动态规划思路。

复制代码
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 i = 1; i < m; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][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++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] =
                    Math.min(dp[i - 1][j], dp[i][j - 1])
                    + grid[i][j];
            }
        }

        return dp[m - 1][n - 1];
    }
}

8. Java 语法解释

8.1 二维数组

复制代码
int[][] grid

对应 C++:

复制代码
vector<vector<int>> grid

8.2 获取行数和列数

Java:

复制代码
int m = grid.length;
int n = grid[0].length;

C++:

复制代码
int m = grid.size();
int n = grid[0].size();

Java 数组使用 .length,没有括号。

8.3 创建二维数组

复制代码
int[][] dp = new int[m][n];

表示创建一个 m × n 的二维整数数组,默认所有元素初始化为 0。

8.4 Math.min

Java:

复制代码
Math.min(a, b)

对应 C++:

复制代码
min(a, b)

所以:

复制代码
dp[i][j] =
    Math.min(dp[i - 1][j], dp[i][j - 1])
    + grid[i][j];

就是:

选择从上面和左边过来时路径和更小的那一个,再加当前格子的值。

9. Python:二维 DP

Python 同样使用相同的 DP。

复制代码
class Solution:    def minPathSum(self, grid):        m = len(grid)        n = len(grid[0])        dp = [[0] * n for _ in range(m)]        dp[0][0] = grid[0][0]        # 初始化第一列        for i in range(1, m):            dp[i][0] = dp[i - 1][0] + grid[i][0]        # 初始化第一行        for j in range(1, n):            dp[0][j] = dp[0][j - 1] + grid[0][j]        # 状态转移        for i in range(1, m):            for j in range(1, n):                dp[i][j] = (                    min(dp[i - 1][j], dp[i][j - 1])                    + grid[i][j]                )        return dp[m - 1][n - 1]

10. Python 语法解释

10.1 获取二维数组大小

复制代码
m = len(grid)n = len(grid[0])

len(grid) 是行数,len(grid[0]) 是列数。

10.2 创建二维数组

复制代码
dp = [[0] * n for _ in range(m)]

例如:

复制代码
m = 3
n = 3

得到:

复制代码
[
    [0,0,0],
    [0,0,0],
    [0,0,0]
]

10.3 Python 的 min

复制代码
min(a, b)

直接得到两个数中更小的一个。

10.4 range(1, m)

复制代码
for i in range(1, m):

表示:

复制代码
i = 1,2,...,m-1

对应 C++:

复制代码
for (int i = 1; i < m; ++i)

11. 空间优化:一维 DP

因为:

复制代码
dp[i][j]

只依赖:

复制代码
上面 dp[i-1][j]
左边 dp[i][j-1]

所以可以只保存一行。

使用:

复制代码
dp[j]

表示当前处理到第 j 列时的最小路径和。

更新前:

复制代码
dp[j] = 上面那个格子的最小路径和

而:

复制代码
dp[j-1] = 当前行左边格子的最小路径和

所以:

复制代码
dp[j] = min(dp[j], dp[j - 1]) + grid[i][j];

空间复杂度可以从:

复制代码
O(m × n)

降低到:

复制代码
O(n)

12. C++:一维空间优化

复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();

        vector<int> dp(n, 0);

        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] =
                    min(dp[j], dp[j - 1])
                    + grid[i][j];
            }
        }

        return dp[n - 1];
    }
};

13. 复杂度

二维 DP:

复制代码
时间复杂度:O(m × n)
空间复杂度:O(m × n)

一维 DP:

复制代码
时间复杂度:O(m × n)
空间复杂度:O(n)

14. 与 LeetCode 63 的联系

两道题几乎是同一个网格 DP 模板。

LeetCode 63:不同路径 II

求:

复制代码
有多少条路线

所以:

复制代码
dp[i][j] =
    dp[i - 1][j] + dp[i][j - 1];

LeetCode 64:最小路径和

求:

复制代码
哪条路线数字之和最小

所以:

复制代码
dp[i][j] =
    min(dp[i - 1][j], dp[i][j - 1])
    + grid[i][j];

它们共同的地方都是:

复制代码
当前位置只能从:
上面
左边
过来

区别只是题目问的东西不同:

复制代码
统计所有方案 → 相加
选最优方案   → min/max

15. 核心记忆

DP 定义

复制代码
dp[i][j]
=
从左上角走到 (i,j)
所能得到的最小路径和

初始化

复制代码
dp[0][0] = grid[0][0];

dp[i][0] =
    dp[i - 1][0] + grid[i][0];

dp[0][j] =
    dp[0][j - 1] + grid[0][j];

状态转移

复制代码
dp[i][j] =
    min(dp[i - 1][j], dp[i][j - 1])
    + grid[i][j];

最后只记一句:

到当前格子的最小路径和 = 上面和左边两个来源中更小的路径和 + 当前格子的数字。

相关推荐
blxr_1 小时前
力扣hot100寻找重复数https://leetcode.cn/problems/find-the-duplicate-number/
算法·leetcode·职场和发展
政企项目老覃1 小时前
金融医疗数据脱敏与隐私计算:从字段打码到联邦建模的落地复盘
程序人生·算法·金融·数据分析
❀͜͡傀儡师1 小时前
Spring Boot 实现图片盲水印:基于 DCT 频域算法的隐形数字水印
spring boot·算法
Zane19942 小时前
贪心算法大多靠不住,为什么Dijkstra的贪心选择偏偏能保证全局最优
算法
CQU_JIAKE2 小时前
9.26【A】
算法
-cywen-2 小时前
GROUNDHOG总体版
人工智能·深度学习·算法
_不会dp不改名_2 小时前
leetcode_1614嵌套括号的最大深度
算法·leetcode·职场和发展
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章110-YOLO 实例分割
图像处理·人工智能·opencv·算法·yolo·计算机视觉
sunburn-2 小时前
Java 排序算法详细教学:从冒泡排序到快速排序
java·开发语言·数据结构·ide·算法