1. 问题背景与核心挑战
不同路径问题是动态规划领域的经典题目,题目描述为:一个机器人位于m×n网格的左上角,每次只能向下或向右移动一步,需要到达网格的右下角,问总共有多少条不同的路径?
这个问题看似简单,但蕴含着多维动态规划的核心思想。我在第一次接触这个问题时,尝试用暴力递归的方法解决,结果发现当网格尺寸稍大(比如10×10)时,计算时间就变得不可接受。这促使我深入理解动态规划的精髓——通过存储子问题的解来避免重复计算。
2. 动态规划解法解析
2.1 状态定义与转移方程
我们定义dp[i][j]表示从起点(0,0)到位置(i,j)的不同路径数。根据题目规则,机器人只能从上方或左方到达当前位置,因此状态转移方程为:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
这个方程看似简单,但需要特别注意边界条件:
- 第一行(i=0)的所有位置只能从左方到达
- 第一列(j=0)的所有位置只能从上方到达
2.2 基础实现代码
python复制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]
这个实现的时间复杂度是O(mn),空间复杂度也是O(mn)。对于大多数面试场景,这个解法已经足够。
3. 空间优化技巧
3.1 滚动数组优化
观察状态转移方程可以发现,我们实际上只需要前一行的数据就可以计算当前行。因此可以将空间复杂度优化到O(n):
python复制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]
3.2 数学组合数解法
这个问题还可以用组合数学来解。从起点到终点需要移动(m-1)+(n-1)步,其中(m-1)步向下,(n-1)步向右。因此路径总数为组合数C((m-1)+(n-1), (m-1))。
python复制import math
def uniquePaths(m: int, n: int) -> int:
return math.comb(m+n-2, m-1)
这个解法的时间复杂度是O(min(m,n)),空间复杂度是O(1)。但在实际应用中要注意大数计算可能带来的性能问题。
4. 常见变种与扩展
4.1 带障碍物的不同路径
如果网格中存在障碍物,我们需要调整状态转移方程:
python复制def uniquePathsWithObstacles(obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
dp = [[0]*n for _ in range(m)]
# 初始化第一行和第一列
dp[0][0] = 1 if obstacleGrid[0][0] == 0 else 0
for i in range(1, m):
dp[i][0] = dp[i-1][0] if obstacleGrid[i][0] == 0 else 0
for j in range(1, n):
dp[0][j] = dp[0][j-1] if obstacleGrid[0][j] == 0 else 0
for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j] == 0:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
else:
dp[i][j] = 0
return dp[-1][-1]
4.2 三维网格的不同路径
如果问题扩展到三维空间,机器人可以向下、向右或向内移动,状态转移方程变为:
dp[i][j][k] = dp[i-1][j][k] + dp[i][j-1][k] + dp[i][j][k-1]
5. 实战技巧与注意事项
-
边界条件处理:在初始化dp数组时,一定要正确处理第一行和第一列的情况。这是动态规划问题中最容易出错的地方。
-
空间优化陷阱:使用滚动数组优化时,要注意遍历顺序。对于二维问题,通常需要从左到右、从上到下遍历。
-
大数处理:当网格尺寸很大时,路径数可能超过普通整数范围。在面试中要确认是否需要处理大数情况。
-
测试用例设计:验证代码时要考虑以下情况:
- 1×1网格(结果为1)
- 1×n或m×1网格(结果为1)
- 包含障碍物的网格
- 较大尺寸的网格(测试性能)
-
调试技巧:对于复杂的动态规划问题,可以打印出dp表格来验证中间结果是否正确。
6. 性能对比与选择建议
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 基础DP | O(mn) | O(mn) | 通用解法,易于理解 |
| 滚动数组 | O(mn) | O(n) | 空间受限时使用 |
| 组合数学 | O(min(m,n)) | O(1) | 无障碍物且不需要具体路径 |
在实际面试中,建议先给出基础DP解法,然后根据面试官的要求逐步优化。同时要能够解释每种解法的优缺点。
7. 相关题目推荐
- 不同路径II(带障碍物版本)
- 最小路径和(动态规划求最优解)
- 三角形最小路径和(变形动态规划)
- 地下城游戏(逆向动态规划)
- 解码方法(一维动态规划)
对于想深入掌握动态规划的学习者,我建议按照以下顺序练习:
- 一维DP问题(如斐波那契数列、爬楼梯)
- 二维DP问题(如不同路径、编辑距离)
- 背包问题系列
- 状态压缩DP
- 树形DP
动态规划的核心在于识别子问题并找到最优子结构。不同路径问题虽然简单,但包含了动态规划的所有关键要素,是理解这一算法思想的绝佳起点。