动态规划¶
动态规划(Dynamic Programming, DP)是一种将复杂问题分解为子问题,通过保存子问题的最优解来避免重复计算,从而高效求解全局最优解的算法思想。
核心思想¶
动态规划的本质是**用空间换时间**,通过记录子问题的解来避免重复计算,将指数级时间复杂度优化为多项式级。
DP vs 递归:
| 特征 | 朴素递归 | 动态规划 |
|---|---|---|
| 子问题重叠 | 重复计算 | 记忆化存储 |
| 时间复杂度 | 通常 \(O(2^n)\) | 通常 \(O(n)\) 或 \(O(n^2)\) |
| 空间复杂度 | \(O(n)\)(递归栈) | \(O(n)\) 或 \(O(n^2)\)(dp 数组) |
| 实现方式 | 自顶向下 | 自底向上或记忆化递归 |
Tip
只要递归状态从 0 开始,备忘录的大小就要+1,比如 Fibonacci、爬楼梯、前缀和,因为 dp[0]也需要存储。
DP 解题五步曲¶
掌握以下框架,可以系统性地解决大部分 DP 问题:
-
定义 dp 数组的含义
-
dp[i]或dp[i][j]表示什么? -
明确状态的物理意义
-
找出状态转移方程
-
当前状态如何由之前的状态推导而来?
-
这是 DP 的核心,也是最难的部分
-
初始化 dp 数组
-
确定边界条件(base case)
-
通常是
dp[0]或dp[0][0] -
确定遍历顺序
-
从前往后还是从后往前?
-
一维还是二维遍历?
-
举例推导 dp 数组
- 用具体例子验证状态转移方程
- 调试时打印 dp 数组观察规律
经典例子:斐波那契数列¶
问题¶
计算斐波那契数列的第 n 项:\(F(n) = F(n-1) + F(n-2)\),其中 \(F(0) = 0, F(1) = 1\)
朴素递归(\(O(2^n)\))¶
DP 优化(\(O(n)\))¶
DP 核心题型¶
1. 线性 DP¶
特征:状态转移只依赖于前面的若干个状态
经典题目:
- 70. Climbing Stairs — 爬楼梯(入门题)
- 198. House Robber — 打家劫舍
- 300. Longest Increasing Subsequence — 最长递增子序列
2. 背包问题¶
特征:在限制条件下选择物品,使得价值最大
| 类型 | 特点 | 经典题目 |
|---|---|---|
| 0-1 背包 | 每个物品只能选一次 | 416. Partition Equal Subset Sum |
| 完全背包 | 每个物品可以选无限次 | 322. Coin Change |
| 多重背包 | 每个物品有数量限制 | - |
3. 区间 DP¶
特征:在一个区间上进行决策,通常需要二维 dp 数组
经典题目:
- 5. Longest Palindromic Substring — 最长回文子串
- 516. Longest Palindromic Subsequence — 最长回文子序列
4. 路径问题¶
特征:在网格或图上寻找路径,通常是二维 dp
经典题目:
- 62. Unique Paths — 不同路径
- 64. Minimum Path Sum — 最小路径和
- 120. Triangle — 三角形最小路径和
5. 字符串 DP¶
特征:涉及字符串匹配、编辑距离等
经典题目:
- 72. Edit Distance — 编辑距离
- 1143. Longest Common Subsequence — 最长公共子序列
0-1 背包详解¶
0-1 背包是 DP 中最经典的问题,掌握它可以解决很多变体。
问题描述¶
有 n 个物品,每个物品有重量 w[i] 和价值 v[i]。背包容量为 capacity。每个物品只能选一次,求背包能装下的最大价值。
状态定义¶
dp[i][j] 表示前 i 个物品,背包容量为 j 时的最大价值。
状态转移方程¶
代码实现¶
DP 优化技巧¶
1. 空间优化¶
如果 dp[i] 只依赖于 dp[i-1],可以用滚动数组或两个变量优化空间:
2. 状态压缩¶
使用位运算压缩状态,适用于状态数量较少的情况。
3. 单调队列优化¶
在某些 DP 问题中,可以用单调队列优化转移过程,将 \(O(n^2)\) 降至 \(O(n)\)。
学习建议¶
- 从简单题开始:先做爬楼梯、斐波那契等入门题
- 掌握经典模型:0-1 背包、完全背包、最长公共子序列
- 多画图推导:手动推导 dp 数组,理解状态转移
- 总结模板:归纳不同类型 DP 的解题模板
- 刷题顺序:线性 DP → 背包 DP → 区间 DP → 树形 DP
经典题目清单¶
- 70. Climbing Stairs — 爬楼梯(入门)
- 198. House Robber — 打家劫舍
- 322. Coin Change — 零钱兑换(完全背包)
- 416. Partition Equal Subset Sum — 分割等和子集(0-1 背包)
- 62. Unique Paths — 不同路径
- 64. Minimum Path Sum — 最小路径和
- 300. Longest Increasing Subsequence — 最长递增子序列
- 1143. Longest Common Subsequence — 最长公共子序列
- 72. Edit Distance — 编辑距离
- 5. Longest Palindromic Substring — 最长回文子串