题目:最小路径和(Minimum Path Sum)
字数 2470 2025-12-22 20:37:40

好的,我注意到你已看过大量题目。我将为你讲解一个未被列出的线性动态规划经典题目。

题目:最小路径和(Minimum Path Sum)

题目描述:

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

规则:每次只能向下或者向右移动一步。

示例:
输入:

grid = [
  [1, 3, 1],
  [1, 5, 1],
  [4, 2, 1]
]

输出:7
解释:因为路径 1 → 3 → 1 → 1 → 1 的总和最小,为 7。


解题思路循序渐进讲解

这是一个典型的二维线性动态规划问题。我们的目标是找到从起点到终点的最小累计代价。

核心思路: 由于只能向右或向下走,到达网格中任意一个点 (i, j) 的最小路径和,只可能来源于它正上方的格子 (i-1, j) 或者它正左方的格子 (i, j-1),再加上当前格子自身的值。我们用一个二维 DP 数组 dp[i][j] 来存储这个“最小路径和”。

步骤 1:定义状态

定义 dp[i][j] 表示:从起点 (0, 0) 走到格子 (i, j) 的最小路径和。

我们的最终答案就是 dp[m-1][n-1]

步骤 2:建立状态转移方程

思考如何到达 (i, j)

  1. 如果从上方 (i-1, j) 下来,那么路径和为 dp[i-1][j] + grid[i][j]
  2. 如果从左方 (i, j-1) 过来,那么路径和为 dp[i][j-1] + grid[i][j]

为了得到最小和,我们取这两种来源的最小值

因此,状态转移方程为:
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]

步骤 3:确定初始条件(Base Cases)

初始条件是动态规划的起点,必须单独处理。

  • 起点 (0, 0): 到达起点的最小路径和就是它自身的值。所以 dp[0][0] = grid[0][0]
  • 第一行 (i = 0): 对于第一行的任何格子 (0, j),它只能从其左边的格子 (0, j-1) 向右移动到达。因此,dp[0][j] = dp[0][j-1] + grid[0][j]
  • 第一列 (j = 0): 对于第一列的任何格子 (i, 0),它只能从其上面的格子 (i-1, 0) 向下移动到达。因此,dp[i][0] = dp[i-1][0] + grid[i][0]

步骤 4:计算顺序

我们需要计算 dp[i][j],必须先知道 dp[i-1][j]dp[i][j-1]
最自然的计算顺序是:从左到右,从上到下,一行一行(或一列一列)地遍历整个网格。
这样,当计算到 (i, j) 时,它上方和左方的状态都已经计算好了。

步骤 5:算法实现(以示例为例,逐步演算)

我们用示例网格来手动演算一遍 dp 表的填充过程。

初始网格 grid

[1, 3, 1]
[1, 5, 1]
[4, 2, 1]
  1. 初始化 dp[0][0]dp[0][0] = grid[0][0] = 1

    dp: [1, ?, ?]
         [?, ?, ?]
         [?, ?, ?]
    
  2. 填充第一行 (i=0)

    • dp[0][1] = dp[0][0] + grid[0][1] = 1 + 3 = 4
    • dp[0][2] = dp[0][1] + grid[0][2] = 4 + 1 = 5
    dp: [1, 4, 5]
         [?, ?, ?]
         [?, ?, ?]
    
  3. 填充第一列 (j=0)

    • dp[1][0] = dp[0][0] + grid[1][0] = 1 + 1 = 2
    • dp[2][0] = dp[1][0] + grid[2][0] = 2 + 4 = 6
    dp: [1, 4, 5]
         [2, ?, ?]
         [6, ?, ?]
    
  4. 填充内部格子 (i=1, j=1)

    • dp[1][1] = min(dp[0][1], dp[1][0]) + grid[1][1] = min(4, 2) + 5 = 2 + 5 = 7
    dp: [1, 4, 5]
         [2, 7, ?]
         [6, ?, ?]
    
  5. 填充内部格子 (i=1, j=2)

    • dp[1][2] = min(dp[0][2], dp[1][1]) + grid[1][2] = min(5, 7) + 1 = 5 + 1 = 6
    dp: [1, 4, 5]
         [2, 7, 6]
         [6, ?, ?]
    
  6. 填充内部格子 (i=2, j=1)

    • dp[2][1] = min(dp[1][1], dp[2][0]) + grid[2][1] = min(7, 6) + 2 = 6 + 2 = 8
    dp: [1, 4, 5]
         [2, 7, 6]
         [6, 8, ?]
    
  7. 填充终点 (i=2, j=2)

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

最终答案 dp[2][2] = 7,与示例一致。

步骤 6:空间优化(进阶思考)

观察状态转移方程 dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j],计算当前行 dp[i][...] 时,只需要用到上一行 dp[i-1][...]当前行已经计算过的左邻居 dp[i][j-1]

因此,我们可以将二维 dp 数组优化为一维数组 dp[j],其长度为 n(列数):

  • dp[j] 在更新前,存储的是上一行第 j 列的值(即旧的 dp[i-1][j])。
  • dp[j-1] 在更新时,存储的是当前行第 j-1 列已经更新过的值(即新的 dp[i][j-1])。

优化后的状态转移变为:
dp[j] = min(dp[j](上一行的值), dp[j-1](当前行左边的值)) + grid[i][j]

同时,需要小心处理第一行和第一列的初始化。

# 空间优化后的Python代码示例
def minPathSum(grid):
    if not grid or not grid[0]:
        return 0

    m, n = len(grid), len(grid[0])
    dp = [0] * n

    # 初始化第一行
    dp[0] = grid[0][0]
    for j in range(1, n):
        dp[j] = dp[j-1] + grid[0][j]

    # 遍历剩余行
    for i in range(1, m):
        # 每行开始时,更新第一列(只能从上方来)
        dp[0] = dp[0] + grid[i][0]
        for j in range(1, n):
            # dp[j] 是上一行j列的值,dp[j-1]是本行已更新的j-1列的值
            dp[j] = min(dp[j], dp[j-1]) + grid[i][j]

    return dp[-1]

总结:
“最小路径和”问题通过定义清晰的状态 dp[i][j],利用只能向右/向下的移动规则,推导出简洁的状态转移方程。正确处理边界条件(第一行、第一列)后,按顺序计算即可。这个问题是理解二维网格上动态规划的绝佳入门,其空间优化技巧也值得掌握。

好的,我注意到你已看过大量题目。我将为你讲解一个未被列出的线性动态规划经典题目。 题目:最小路径和(Minimum Path Sum) 题目描述: 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角 (0, 0) 到右下角 (m-1, n-1) 的路径,使得路径上的数字总和为最小。 规则:每次只能 向下 或者 向右 移动一步。 示例: 输入: 输出: 7 解释:因为路径 1 → 3 → 1 → 1 → 1 的总和最小,为 7。 解题思路循序渐进讲解 这是一个典型的 二维线性动态规划 问题。我们的目标是找到从起点到终点的最小累计代价。 核心思路: 由于只能向右或向下走,到达网格中任意一个点 (i, j) 的最小路径和,只可能来源于它 正上方 的格子 (i-1, j) 或者它 正左方 的格子 (i, j-1) ,再加上当前格子自身的值。我们用一个二维 DP 数组 dp[i][j] 来存储这个“最小路径和”。 步骤 1:定义状态 定义 dp[i][j] 表示:从起点 (0, 0) 走到格子 (i, j) 的最小路径和。 我们的最终答案就是 dp[m-1][n-1] 。 步骤 2:建立状态转移方程 思考如何到达 (i, j) : 如果从 上方 (i-1, j) 下来,那么路径和为 dp[i-1][j] + grid[i][j] 。 如果从 左方 (i, j-1) 过来,那么路径和为 dp[i][j-1] + grid[i][j] 。 为了得到最小和,我们取这两种来源的 最小值 。 因此,状态转移方程为: dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j] 步骤 3:确定初始条件(Base Cases) 初始条件是动态规划的起点,必须单独处理。 起点 (0, 0) : 到达起点的最小路径和就是它自身的值。所以 dp[0][0] = grid[0][0] 。 第一行 (i = 0) : 对于第一行的任何格子 (0, j) ,它只能从其左边的格子 (0, j-1) 向右移动到达。因此, dp[0][j] = dp[0][j-1] + grid[0][j] 。 第一列 (j = 0) : 对于第一列的任何格子 (i, 0) ,它只能从其上面的格子 (i-1, 0) 向下移动到达。因此, dp[i][0] = dp[i-1][0] + grid[i][0] 。 步骤 4:计算顺序 我们需要计算 dp[i][j] ,必须先知道 dp[i-1][j] 和 dp[i][j-1] 。 最自然的计算顺序是: 从左到右,从上到下 ,一行一行(或一列一列)地遍历整个网格。 这样,当计算到 (i, j) 时,它上方和左方的状态都已经计算好了。 步骤 5:算法实现(以示例为例,逐步演算) 我们用示例网格来手动演算一遍 dp 表的填充过程。 初始网格 grid : 初始化 dp[0][0] : dp[0][0] = grid[0][0] = 1 。 填充第一行 (i=0) : dp[0][1] = dp[0][0] + grid[0][1] = 1 + 3 = 4 dp[0][2] = dp[0][1] + grid[0][2] = 4 + 1 = 5 填充第一列 (j=0) : dp[1][0] = dp[0][0] + grid[1][0] = 1 + 1 = 2 dp[2][0] = dp[1][0] + grid[2][0] = 2 + 4 = 6 填充内部格子 (i=1, j=1) : dp[1][1] = min(dp[0][1], dp[1][0]) + grid[1][1] = min(4, 2) + 5 = 2 + 5 = 7 填充内部格子 (i=1, j=2) : dp[1][2] = min(dp[0][2], dp[1][1]) + grid[1][2] = min(5, 7) + 1 = 5 + 1 = 6 填充内部格子 (i=2, j=1) : dp[2][1] = min(dp[1][1], dp[2][0]) + grid[2][1] = min(7, 6) + 2 = 6 + 2 = 8 填充终点 (i=2, j=2) : dp[2][2] = min(dp[1][2], dp[2][1]) + grid[2][2] = min(6, 8) + 1 = 6 + 1 = 7 最终答案 dp[2][2] = 7 ,与示例一致。 步骤 6:空间优化(进阶思考) 观察状态转移方程 dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j] ,计算当前行 dp[i][...] 时,只需要用到 上一行 dp[i-1][...] 和 当前行已经计算过的左邻居 dp[i][j-1] 。 因此,我们可以将二维 dp 数组优化为一维数组 dp[j] ,其长度为 n (列数): dp[j] 在更新前,存储的是上一行第 j 列的值(即旧的 dp[i-1][j] )。 dp[j-1] 在更新时,存储的是当前行第 j-1 列已经更新过的值(即新的 dp[i][j-1] )。 优化后的状态转移变为: dp[j] = min(dp[j](上一行的值), dp[j-1](当前行左边的值)) + grid[i][j] 同时,需要小心处理第一行和第一列的初始化。 总结: “最小路径和”问题通过定义清晰的状态 dp[i][j] ,利用只能向右/向下的移动规则,推导出简洁的状态转移方程。正确处理边界条件(第一行、第一列)后,按顺序计算即可。这个问题是理解二维网格上动态规划的绝佳入门,其空间优化技巧也值得掌握。