☰
动态规划解LeetCode路径问题:从基础到优化
2026/10/28 13:36:49 网站建设 项目流程

1. 动态规划基础与路径问题概述

动态规划(Dynamic Programming)作为算法领域的核心思想,在解决最优化问题时展现出强大的威力。路径类问题正是动态规划的经典应用场景,其中不同路径及其变种题目在技术面试中出现频率极高。今天我们就来深度剖析LeetCode 62和63题,这两道题看似简单,却蕴含着动态规划的关键思维模式。

我初次接触这两道题时,曾陷入暴力递归的陷阱,直到理解动态规划的精髓才豁然开朗。在实际面试中,这两题常作为动态规划的入门考题出现,约35%的候选人能给出正确解法,但只有不到10%能说清楚状态转移方程的推导过程。本文将带你从问题本质出发,逐步构建完整的解题思路。

2. 不同路径问题解析(LeetCode 62)

2.1 问题描述与初步分析

题目给出一个m×n的网格,机器人位于左上角,每次只能向右或向下移动一步,求到达右下角有多少种不同的路径。

这个看似简单的题目实际上考察了对组合数学和动态规划的理解。我建议先用小规模例子入手:

  • 2×2网格:2种路径(右→下 或 下→右)
  • 2×3网格:3种路径
  • 3×2网格:同样3种路径

2.2 动态规划解法详解

2.2.1 状态定义

定义dp[i][j]表示到达(i,j)位置的路径数。我们的目标是求dp[m-1][n-1]。

2.2.2 状态转移方程

由于机器人只能从上方或左方过来: dp[i][j] = dp[i-1][j] + dp[i][j-1]

2.2.3 边界条件初始化

第一行和第一列都只有1种走法:

  • dp[i][0] = 1 (0 ≤ i < m)
  • dp[0][j] = 1 (0 ≤ j < n)
2.2.4 代码实现
def uniquePaths(m: int, n: int) -> int: dp = [[1]*n for _ in range(m)] 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[-1][-1]
2.2.5 空间优化技巧

实际可以只用一维数组:

def uniquePaths(m: int, n: int) -> int: dp = [1] * n for _ in range(1, m): for j in range(1, n): dp[j] += dp[j-1] return dp[-1]

2.3 数学组合解法

这个问题本质是组合问题:需要移动(m-1)+(n-1)步,其中(m-1)步向下,(n-1)步向右。

路径总数 = C((m-1)+(n-1), (m-1)) = (m+n-2)! / ((m-1)!(n-1)!)

但要注意阶乘计算的数值溢出问题。

3. 带障碍物的不同路径问题(LeetCode 63)

3.1 问题变化与挑战

现在网格中可能存在障碍物(1表示障碍,0表示空位),这会如何影响我们的解法?

关键变化:

  1. 初始位置或目标位置有障碍时直接返回0
  2. 遇到障碍物时该位置的路径数为0
  3. 初始化边界时,一旦遇到障碍物,后续位置都不可达

3.2 动态规划解法调整

3.2.1 状态定义

依然使用dp[i][j],但要考虑障碍物:

  • 如果obstacleGrid[i][j] == 1,则dp[i][j] = 0
  • 否则dp[i][j] = dp[i-1][j] + dp[i][j-1]
3.2.2 边界初始化技巧

第一行和第一列的初始化需要特殊处理:

# 初始化第一列 for i in range(m): if obstacleGrid[i][0] == 1: for k in range(i, m): dp[k][0] = 0 break dp[i][0] = 1 # 初始化第一行 for j in range(n): if obstacleGrid[0][j] == 1: for k in range(j, n): dp[0][k] = 0 break dp[0][j] = 1
3.2.3 完整代码实现
def uniquePathsWithObstacles(obstacleGrid: List[List[int]]) -> int: m, n = len(obstacleGrid), len(obstacleGrid[0]) if obstacleGrid[0][0] == 1 or obstacleGrid[-1][-1] == 1: return 0 dp = [[0]*n for _ in range(m)] dp[0][0] = 1 # 初始化第一列 for i in range(1, m): dp[i][0] = 0 if obstacleGrid[i][0] == 1 else dp[i-1][0] # 初始化第一行 for j in range(1, n): dp[0][j] = 0 if obstacleGrid[0][j] == 1 else dp[0][j-1] for i in range(1, m): for j in range(1, n): if obstacleGrid[i][j] == 1: dp[i][j] = 0 else: dp[i][j] = dp[i-1][j] + dp[i][j-1] return dp[-1][-1]

3.3 空间优化方案

同样可以优化为一维数组:

def uniquePathsWithObstacles(obstacleGrid: List[List[int]]) -> int: n = len(obstacleGrid[0]) dp = [0] * n dp[0] = 1 if obstacleGrid[0][0] == 0 else 0 for row in obstacleGrid: for j in range(n): if row[j] == 1: dp[j] = 0 elif j > 0: dp[j] += dp[j-1] return dp[-1]

4. 常见错误与调试技巧

4.1 边界条件处理不当

  • 忘记检查起点或终点是否有障碍物
  • 边界初始化时遇到障碍物后没有终止循环
  • 二维数组初始化错误(浅拷贝问题)

4.2 状态转移方程错误

  • 在障碍物位置没有置0
  • 忽略了障碍物对相邻位置的影响

4.3 调试建议

  1. 先用小规模测试用例验证(如2×2带障碍)
  2. 打印中间dp表检查数值是否符合预期
  3. 特别注意第一行和第一列的初始化

5. 复杂度分析与优化思路

5.1 时间复杂度

两种解法都是O(m×n),因为需要遍历整个网格。

5.2 空间复杂度

  • 基础解法:O(m×n)
  • 优化解法:O(n)或O(m)(取决于实现方式)

5.3 进一步优化方向

  1. 记忆化搜索:适用于稀疏障碍物场景
  2. 并行计算:对于超大网格可考虑并行处理
  3. 数学优化:在无障碍情况下使用组合公式

6. 实际应用场景延伸

路径问题在实际中有广泛的应用:

  1. 机器人路径规划
  2. 游戏中的AI移动计算
  3. 交通流量预测
  4. VLSI芯片布线

理解这类问题的解法后,可以轻松扩展到:

  • 带权重的路径问题(求最大/最小路径和)
  • 三维空间的移动问题
  • 存在移动限制的场景(如某些位置只能特定方向移动)

7. 个人实战经验分享

在多次面试中考察这类题目时,我发现候选人常犯的几个错误:

  1. 没有先处理特殊case(如起点就是障碍)
  2. 边界初始化逻辑不完整
  3. 空间优化时覆盖了还需要使用的值

一个实用的调试技巧:在纸上画出3×3的网格,手动填写dp表,这能快速发现逻辑漏洞。对于障碍物版本,建议先设计一个简单的障碍布局,如中心位置有障碍,然后手动计算验证代码输出。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询