动态规划(Dynamic Programming)作为算法领域的核心思想,长期困扰着许多学习者。我见过太多人一上来就死记硬背"状态转移方程",却始终无法真正理解其精髓。实际上,动态规划的本质在于将复杂问题分解为重叠子问题,通过记忆化存储避免重复计算。
初学者常见的三大误区:
重要提示:动态规划不是某种特定算法,而是一种方法论。就像武术中的心法,需要先练好基本功(暴力递归),再逐步进阶到高阶技巧(状态压缩)。
假设有n阶楼梯,每次可以跨1或2步,问有多少种走法。这是理解DP最经典的入门题。
暴力递归解法:
python复制def climb_stairs(n):
if n == 1: return 1
if n == 2: return 2
return climb_stairs(n-1) + climb_stairs(n-2)
这个解法虽然直观,但存在严重的重复计算问题。比如计算climb_stairs(5)时会重复计算climb_stairs(3)多次。
通过添加缓存来存储已计算结果:
python复制memo = {}
def climb_stairs(n):
if n in memo: return memo[n]
if n == 1: return 1
if n == 2: return 2
memo[n] = climb_stairs(n-1) + climb_stairs(n-2)
return memo[n]
时间复杂度从O(2^n)降到O(n),这就是DP的核心优势——通过空间换时间。
将递归改为迭代,显式定义DP数组:
python复制def climb_stairs(n):
if n <= 2: return n
dp = [0]*(n+1)
dp[1], dp[2] = 1, 2
for i in range(3, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
这种写法更符合动态规划的"表格法"特征,也是面试中最常见的实现方式。
对于爬楼梯问题,状态转移方程为:
code复制dp[n] = dp[n-1] + dp[n-2]
这表示到达第n阶的方法数等于:
观察发现当前状态只依赖前两个状态,因此可以压缩空间:
python复制def climb_stairs(n):
if n <= 2: return n
a, b = 1, 2
for _ in range(3, n+1):
a, b = b, a+b
return b
空间复杂度从O(n)降到O(1),这是DP优化的常见手段。
给定物品重量weights和价值values,背包容量capacity,每个物品只能选一次,求最大价值。
定义dp[i][j]表示前i个物品在容量j时的最大价值:
python复制def knapsack(weights, values, capacity):
n = len(weights)
dp = [[0]*(capacity+1) for _ in range(n+1)]
for i in range(1, n+1):
for j in range(1, capacity+1):
if weights[i-1] > j:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], values[i-1]+dp[i-1][j-weights[i-1]])
return dp[n][capacity]
由于每行只依赖上一行,可以压缩为一维数组:
python复制def knapsack(weights, values, capacity):
dp = [0]*(capacity+1)
for i in range(len(weights)):
for j in range(capacity, weights[i]-1, -1):
dp[j] = max(dp[j], values[i]+dp[j-weights[i]])
return dp[capacity]
注意内循环必须倒序,否则会重复计算物品。
个人经验:动态规划的学习曲线陡峭但回报巨大。建议每周专门安排2-3小时进行专题训练,连续2个月后会有质的飞跃。我自己的突破点是做完50道DP题后突然开窍,发现各种变体其实都是相通的。