返回

从动态规划到记忆化搜索:LeetCode 62 不同路径详解

后端

LeetCode 62:不同路径 - 动态规划与记忆化搜索的经典运用

导读

LeetCode 62 不同路径是动态规划领域的经典问题,旨在求解从网格的左上角移动到右下角的不同路径数,其中只能向下或向右移动。本文将深入探讨动态规划和记忆化搜索在解决此问题中的应用,并提供清晰的代码示例和详细的解释。

动态规划:分解与求解

动态规划是一种强大的算法范式,通过将复杂问题分解为更小的子问题,并保存子问题的解以避免重复计算,从而高效求解。

在不同路径问题中,我们可以将网格划分为多个子网格,每个子网格代表从左上角移动到该子网格的不同路径数。通过递推计算子网格的不同路径数,最终得到从左上角移动到右下角的不同路径总数。

代码示例:动态规划

def unique_paths(m, n):
  # 创建一个二维数组 dp 存储子网格的不同路径数
  dp = [[0] * n for _ in range(m)]

  # 初始化第一行和第一列
  for i in range(m):
    dp[i][0] = 1
  for j in range(n):
    dp[0][j] = 1

  # 递推计算其他子网格的不同路径数
  for i in range(1, m):
    for j in range(1, n):
      dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

  # 返回右下角子网格的不同路径数
  return dp[m - 1][n - 1]

记忆化搜索:优化计算

记忆化搜索是动态规划的一种变体,它在计算子问题解之前,先检查该子问题是否已经计算过。如果已经计算过,则直接返回保存的解,否则才进行计算并保存解。

这样一来,记忆化搜索避免了重复计算,大大优化了算法的性能。

代码示例:记忆化搜索

def unique_paths_memo(m, n):
  # 创建一个字典 memo 存储已经计算过的子问题解
  memo = {}

  # 辅助函数,计算从 (i, j) 到右下角的不同路径数
  def dfs(i, j):
    # 如果 (i, j) 超出网格范围,返回 0
    if i >= m or j >= n:
      return 0

    # 如果 (i, j) 为右下角,返回 1
    if i == m - 1 and j == n - 1:
      return 1

    # 检查 (i, j) 是否已经计算过
    key = (i, j)
    if key in memo:
      return memo[key]

    # 计算从 (i, j) 到右下角的不同路径数
    count = dfs(i + 1, j) + dfs(i, j + 1)

    # 将 (i, j) 的不同路径数保存到 memo 中
    memo[key] = count

    # 返回不同路径数
    return count

  # 从左上角开始计算不同路径数
  return dfs(0, 0)

结论

LeetCode 62 不同路径问题展示了动态规划和记忆化搜索在解决复杂问题时的强大威力。通过分解问题并保存子问题的解,这些算法可以高效求解问题,即使面对规模较大的输入也能保持良好的性能。

常见问题解答

  1. 动态规划和记忆化搜索有什么区别?

    • 动态规划将问题分解为子问题,并存储子问题的解以避免重复计算。记忆化搜索在计算子问题解之前,会先检查该子问题是否已经计算过。
  2. 哪种方法更适合解决不同路径问题?

    • 对于规模较小的输入,动态规划和记忆化搜索都可以高效求解。对于规模较大的输入,记忆化搜索由于避免了重复计算,具有更好的性能优势。
  3. 不同路径问题的最优解是什么?

    • 从左上角到右下角的不同路径数为网格中方块数的阶乘除以第一行和第一列方块数的阶乘之和。
  4. LeetCode 62 问题是否有其他求解方法?

    • 除了动态规划和记忆化搜索之外,还可以使用组合数学方法求解此问题。
  5. 如何提高不同路径问题的求解效率?

    • 使用滚动数组或位运算优化空间复杂度。
    • 对于特殊网格(例如只有障碍物的网格),使用图论算法(如深度优先搜索或广度优先搜索)求解。