1. 动态规划算法精要解析
动态规划(Dynamic Programming)作为算法领域的核心方法论,在LeetCode Hot 100等高频题库中占比超过30%。我第一次系统刷Hot 100时,花了整整两周时间才突破动态规划题型,后来总结出这套可复用的解题框架。
动态规划本质上是通过"空间换时间"来优化递归问题的解法。其核心特征包含重叠子问题和最优子结构——前者指问题可分解为重复计算的子问题,后者意味全局最优解可由局部最优解组合得到。以经典的爬楼梯问题为例,要到达第n阶,要么从n-1阶跨一步,要么从n-2阶跨两步,因此递推公式为f(n)=f(n-1)+f(n-2)。
关键认知:动态规划不是具体算法,而是方法论框架,包含自顶向下(记忆化递归)和自底向上(迭代填表)两种实现范式。
1.1 动态规划三大要素
- 状态定义:明确dp数组的含义。比如背包问题中dp[i][j]表示考虑前i件物品在容量j时的最大价值
- 状态转移方程:确定状态间的递推关系。如股票问题中:
python复制dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) # 第i天不持股 dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]) # 第i天持股 - 边界条件:初始化基础状态。例如编辑距离问题中dp[0][j]=j(全插入操作)
1.2 Hot 100经典题型分类
| 类型 | 代表题目 | 核心解法 |
|---|---|---|
| 线性DP | 最大子数组和(#53) | Kadane算法 |
| 区间DP | 最长回文子串(#5) | 中心扩展法 |
| 背包问题 | 零钱兑换(#322) | 完全背包模板 |
| 状态机DP | 买卖股票最佳时机(#121) | 持有/未持有双状态 |
| 树形DP | 打家劫舍III(#337) | 后序遍历+状态转移 |
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动态规划解题四步法
2.1 问题拆解阶段
以Hot 100第70题爬楼梯为例:
- 暴力递归解法存在O(2^n)时间复杂度
- 观察发现f(5)=f(4)+f(3),存在重复计算
- 符合动态规划适用条件
2.2 状态定义技巧
对于二维DP问题,我常用这两种定义方式:
- 序列型:dp[i]表示前i个元素的最优解(如LIS问题)
- 区间型:dp[i][j]表示区间i到j的最优解(如石子合并问题)
避坑指南:状态定义要包含足够信息来推导下一个状态,但避免过度设计。比如股票问题引入交易次数维度时,k>n/2时可优化为无限次。
2.3 状态转移方程推导
举例Hot 100第322题零钱兑换:
python复制def coinChange(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for i in range(coin, amount + 1):
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
这里的状态转移体现完全背包思想:dp[i] = min(dp[i], dp[i-coin]+1)
2.4 空间优化策略
当状态转移只依赖有限前驱状态时,可压缩DP表:
- 滚动数组:斐波那契数列只需维护前两个状态
- 降维打击:01背包问题可将二维dp优化为一维
- 状态复用:有些问题可以用变量替代数组(如股票问题)
3. Hot 100高频题型精讲
3.1 序列问题专题
最长递增子序列(LIS)
- 标准解法O(n^2):dp[i]表示以nums[i]结尾的LIS长度
- 优化解法O(nlogn):维护tails数组+二分查找
编辑距离问题
python复制def minDistance(word1, word2):
m, n = len(word1), len(word2)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1): dp[i][0] = i
for j in range(n+1): dp[0][j] = j
for i in range(1, m+1):
for j in range(1, n+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
return dp[m][n]
3.2 背包问题实战
01背包模板
python复制def knapsack(W, wt, val):
n = len(wt)
dp = [[0]*(W+1) for _ in range(n+1)]
for i in range(1, n+1):
for w in range(1, W+1):
if wt[i-1] <= w:
dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]], dp[i-1][w])
else:
dp[i][w] = dp[i-1][w]
return dp[n][W]
完全背包变式
- 零钱兑换问题(求最少硬币数)
- 组合总和IV(求排列数)
4. 动态规划调试技巧
4.1 常见错误排查
-
初始化错误:边界条件未正确处理
- 示例:在打家劫舍问题中忘记初始化dp[0]=nums[0], dp[1]=max(nums[0],nums[1])
-
状态转移遗漏:未考虑所有可能转移路径
- 比如在股票问题中忘记冷冻期状态
-
维度错乱:二维DP行列定义混淆
- 建议在代码中添加注释说明dp[i][j]含义
4.2 可视化调试法
对于复杂DP问题,我习惯:
- 打印DP表观察填充过程
- 用Excel手动模拟小规模案例
- 绘制状态转移图理清关系
例如解决Hot 100第72题编辑距离时,可以打印出如下DP表:
code复制 Ø a b c d
Ø 0 1 2 3 4
a 1 0 1 2 3
b 2 1 0 1 2
c 3 2 1 0 1
4.3 时间复杂度优化
当遇到TLE时考虑:
- 状态定义是否冗余(如股票问题中的k维度优化)
- 能否用单调队列/栈优化(如滑动窗口最大值)
- 数学规律剪枝(如整数拆分问题)
5. 动态规划进阶路线
5.1 从Hot 100到竞赛难度
建议刷题顺序:
- 基础线性DP(斐波那契、爬楼梯)
- 经典背包问题(01背包、完全背包)
- 区间DP(石子合并、多边形三角剖分)
- 状态压缩DP(旅行商问题)
- 数位DP(数字1的个数)
5.2 竞赛级优化技巧
- 斜率优化:将转移方程转化为y=kx+b形式
- 四边形不等式:优化区间DP决策单调性
- 插头DP:处理网格图上的连通性问题
我在训练中发现,掌握动态规划的关键在于:
- 至少亲手推导20道经典问题的状态转移方程
- 对同一问题尝试不同状态定义(如背包问题的两种维度顺序)
- 建立自己的错题本,记录非常规的状态转移思路
