返回

算法巧思,轻松拿下最小路径和

后端

64. 最小路径和:动态规划的艺术

在算法的浩瀚海洋中,总有一些题目令人眼前一亮,激发我们的思考,让我们在解题的过程中不断精进。今天,我们来共同探讨 LeetCode 上的经典题目——64. 最小路径和

题目

我们给定一个由非负整数构成的 m x n 矩阵,代表一个从左上角到右下角的网格。在该网格中,从左上角到右下角的每个单元格都代表一个必须经过的障碍物。网格中的每个单元格的值表示通过该单元格所需的额外费用。

我们的目标是找到一条从左上角到右下角的路径,使得该路径上的总费用最小。

解题思路

乍一看,这道题似乎是一道典型的动态规划问题。动态规划是一种强大的算法设计范式,它将问题分解成子问题,并存储子问题的解,以避免重复计算。

对于最小路径和问题,我们不妨从动态规划的角度思考一下。

设 dp[i][j] 表示从左上角走到第 i 行第 j 列的最小路径和。根据动态规划的一般套路,我们可以得到如下递推公式:

dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]

其中,grid[i][j] 表示第 i 行第 j 列的单元格值。

然而,细心观察,我们不难发现这个递推公式有一个潜在问题。如果我们按照这个递推公式进行计算,会导致我们每次只能向右或者向下移动一格。这显然不符合我们寻找最小路径和的要求,因为我们可能需要经过多个单元格才能到达右下角。

优化思路

为了解决这个问题,我们需要对递推公式进行优化。我们不再限制每次只能向右或者向下移动一格,而是考虑所有可能的路径,并选择其中费用最小的路径。

具体来说,我们修改后的递推公式如下:

dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + grid[i][j]

在这个公式中,我们额外考虑了从左上角走到第 i 行第 j 列之前一格的最小路径和 dp[i - 1][j - 1]。这样,我们就可以选择从三个方向中经过的路径中最小的一条。

代码实现

Python 代码实现如下:

def min_path_sum(grid):
    m, n = len(grid), len(grid[0])
    dp = [[0 for _ in range(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], dp[i - 1][j - 1]) + grid[i][j]

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

总结

解题的关键在于意识到我们可以从多个方向移动,并选择其中费用最小的路径。通过优化后的递推公式,我们成功解决了这个问题,并得到了最小路径和的正确解。

希望这篇文章能带给你算法思维的启发,下次遇到类似的问题时,你就能轻松应对啦!

常见问题解答

  1. 为什么需要对递推公式进行优化?
    因为原始的递推公式限制了我们的移动方向,无法找到最小路径和。

  2. 优化后的递推公式考虑了哪些移动方向?
    左、右、上三个方向。

  3. 代码中 dp 数组的作用是什么?
    dp[i][j] 存储了从左上角走到第 i 行第 j 列的最小路径和。

  4. 代码中 for 循环是如何工作的?
    外层循环负责遍历行,内层循环负责遍历列,并计算每个单元格的最小路径和。

  5. 如何使用代码解决最小路径和问题?
    只需将输入网格 grid 传递给 min_path_sum() 函数,即可得到最小路径和。