1. 问题描述与理解
LeetCode 62题"不同路径"是一个经典的动态规划问题。题目描述如下:一个机器人位于一个 m x n 网格的左上角,每次只能向下或者向右移动一步。机器人试图达到网格的右下角,问总共有多少条不同的路径?
这个问题看似简单,但蕴含着动态规划的核心思想。我第一次遇到这个问题时,直觉告诉我可以用递归来解决,但很快就发现递归的效率太低。后来通过分析,发现这是一个典型的可以用动态规划优化的问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路分析
2.1 暴力递归法
最直观的解法是使用递归。对于网格中的每个点(i,j),到达它的路径数等于从上方(i-1,j)来的路径数加上从左方(i,j-1)来的路径数。递归的终止条件是到达第一行或第一列的任何位置,因为在这些位置只有一条路径(要么一直向右,要么一直向下)。
python复制def uniquePaths(m, n):
if m == 1 or n == 1:
return 1
return uniquePaths(m-1, n) + uniquePaths(m, n-1)
这种方法虽然简单直观,但时间复杂度是指数级的O(2^(m+n)),在LeetCode上会超时。
2.2 动态规划解法
为了提高效率,我们可以使用动态规划。创建一个二维数组dp,其中dp[i][j]表示到达(i,j)位置的不同路径数。根据问题描述,可以得到状态转移方程:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
初始化条件是第一行和第一列的所有位置都只有1条路径。
python复制def uniquePaths(m, n):
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)。
2.3 空间优化解法
观察状态转移方程可以发现,我们只需要前一行的数据就可以计算当前行的数据。因此可以将空间复杂度优化到O(n)。
python复制def uniquePaths(m, n):
dp = [1] * n
for _ in range(1, m):
for j in range(1, n):
dp[j] += dp[j-1]
return dp[-1]
这种优化在m和n较大时能显著减少内存使用。
3. 数学组合解法
这个问题还可以用数学中的组合数学来解决。从左上角到右下角,机器人需要移动m+n-2步,其中m-1步向下,n-1步向右。因此路径总数就是从m+n-2步中选择m-1步向下的组合数。
组合数公式为:C(m+n-2, m-1) = (m+n-2)! / ((m-1)! * (n-1)!)
python复制import math
def uniquePaths(m, n):
return math.comb(m+n-2, m-1)
这种方法的时间复杂度是O(1),但要注意阶乘计算可能导致的数值溢出问题。
4. 边界条件与特殊情况处理
在实际编码中,需要考虑以下边界情况:
- m或n为1时,只有一条路径
- m或n为0时,应该返回0(虽然题目保证输入为正整数)
- 大数情况下的数值溢出(特别是使用数学解法时)
5. 性能对比与选择
三种解法的性能对比如下:
- 递归法:简单但效率低,仅适用于小规模问题
- 动态规划:中等效率,适用于大多数情况
- 数学解法:最高效,但要注意数值限制
在实际应用中,动态规划解法是最平衡的选择,既保证了效率,又不会受到数值限制的影响。
6. 代码实现细节
在实现动态规划解法时,有几个细节需要注意:
- Python中创建二维数组的正确方式:使用列表推导式
[[1]*n for _ in range(m)],避免使用[[1]*n]*m,后者会导致行之间的引用问题 - 循环从1开始,因为第一行和第一列已经初始化为1
- 空间优化版本中,内层循环的顺序很重要,必须从左到右计算
7. 测试用例设计
好的测试用例应该包括:
- 最小网格:1x1
- 单行或单列网格:1xn或mx1
- 正方形网格:nxn
- 长方形网格:m≠n
- 较大网格:测试性能
例如:
python复制assert uniquePaths(1, 1) == 1
assert uniquePaths(3, 2) == 3
assert uniquePaths(7, 3) == 28
assert uniquePaths(3, 7) == 28
assert uniquePaths(10, 10) == 48620
8. 常见错误与调试
新手常犯的错误包括:
- 递归解法没有记忆化,导致重复计算
- 二维数组初始化错误,导致行之间共享引用
- 边界条件处理不当,如m或n为1时
- 空间优化版本中循环顺序错误
- 数学解法中阶乘计算溢出
调试时可以:
- 打印dp表格查看中间结果
- 对小网格手动计算验证
- 使用LeetCode的自定义测试功能
9. 扩展思考
这个问题有几个有趣的变种:
- 如果网格中有障碍物怎么办?(LeetCode 63题)
- 如果要求具体路径而不仅是计数?
- 如果移动方向扩展到四个方向?
- 如果网格非常大,如何进一步优化?
这些变种可以帮助深入理解动态规划的应用场景和优化技巧。
10. 实际应用场景
虽然这个问题看起来是理论性的,但实际应用很广泛:
- 机器人路径规划
- 游戏中的AI移动计算
- 交通流量分析
- 电路布线
- 生物信息学中的路径分析
理解这个问题的解法,可以为解决更复杂的实际问题打下基础。
11. 学习建议
对于想掌握动态规划的初学者,我建议:
- 先理解递归解法,明确问题分解方式
- 画出小网格的dp表格,手动填充
- 实现基础动态规划解法
- 尝试空间优化
- 最后考虑数学解法
- 多做类似题目巩固(如斐波那契、爬楼梯等)
记住,动态规划的关键是:
- 定义好状态
- 找到状态转移方程
- 确定初始条件
- 考虑优化空间
12. 个人心得
我在解决这个问题时,最大的收获是理解了动态规划的空间优化技巧。最初我总是习惯性地使用二维数组,但通过这个问题学会了如何分析状态依赖关系,将空间复杂度从O(mn)降到O(n)。
另一个重要的体会是,同一个问题可以有多种解法,每种解法各有优劣。作为程序员,我们需要根据具体场景选择最合适的解法,而不是一味追求"最优"解法。有时候代码的可读性和可维护性比极致的性能更重要。
