7.DP算法
DP
在C++中,动态规划(Dynamic Programming,DP)是一种通过将复杂问题分解为重叠子问题来高效求解的算法设计范式。以下是DP算法的核心要点和实现方法:
一、动态规划的核心思想
- 重叠子问题:问题可分解为多个重复的子问题(避免重复计算)
- 最优子结构:问题的最优解包含子问题的最优解
- 状态转移方程:定义如何从子问题的解推导出原问题的解
二、动态规划的典型实现步骤
- 定义状态
明确dp[i]
或dp[i][j]
的含义(如dp[i]
表示前i
个元素的最优解) - 初始化状态
设置初始条件(如dp[0] = 0
) - 推导状态转移方程
建立递推关系(如dp[i] = max(dp[i-1], ...)
) - 确定遍历顺序
确保计算当前状态时所需的前置状态已被计算 - 处理结果
从最终状态或中间状态提取答案
三、经典问题与C++代码示例
1. 斐波那契数列(基础DP)
int fib(int n) {
if (n <= 1) return n;
vector<int> dp(n+1);
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; ++i) {
dp[i] = dp[i-1] + dp[i-2]; // 状态转移
}
return dp[n];
}
2. 0-1背包问题(二维DP)
int knapsack(vector<int>& weights, vector<int>& values, int capacity) {
int n = weights.size();
vector<vector<int>> dp(n+1, vector<int>(capacity+1, 0));
for (int i = 1; i <= n; ++i) {
for (int w = 1; w <= capacity; ++w) {
if (weights[i-1] > w) {
dp[i][w] = dp[i-1][w];
} else {
dp[i][w] = max(
dp[i-1][w],
dp[i-1][w - weights[i-1]] + values[i-1]
);
}
}
}
return dp[n][capacity];
}
3. 最长公共子序列(LCS,双序列DP)
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size(), n = text2.size();
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (text1[i-1] == text2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[m][n];
}
四、优化技巧
-
状态压缩:将二维DP优化为一维
// 0-1背包优化版(滚动数组) int knapsack(vector<int>& weights, vector<int>& values, int capacity) { vector<int> dp(capacity+1, 0); for (int i = 0; i < weights.size(); ++i) { for (int w = capacity; w >= weights[i]; --w) { // 逆向遍历 dp[w] = max(dp[w], dp[w - weights[i]] + values[i]); } } return dp[capacity]; }
-
备忘录法:递归+缓存(自顶向下)
unordered_map<int, int> memo; int fib(int n) { if (n <= 1) return n; if (memo.count(n)) return memo[n]; return memo[n] = fib(n-1) + fib(n-2); }
五、注意事项
- 避免状态定义模糊:明确每个维度代表的具体含义
- 边界条件处理:如数组索引越界、初始值设置错误
- 空间优化陷阱:状态压缩时注意遍历顺序(如背包问题必须逆向遍历)
- 避免过度优化:先保证正确性,再考虑优化
六、典型应用场景
问题类型 | 示例问题 |
---|---|
线性DP | 最大子数组和、爬楼梯 |
区间DP | 矩阵链乘法、回文分割 |
树形DP | 二叉树中的最大路径和 |
状态机DP | 股票买卖系列问题 |
位运算DP | 旅行商问题(TSP) |
七、调试技巧
- 打印DP表格观察状态变化
- 小规模测试用例验证
- 对比暴力递归与DP解法结果
动态规划的难点在于找到正确的状态定义和推导状态转移方程。建议通过经典问题(如背包、LCS、编辑距离等)积累经验,逐步掌握这一强大的算法范式。