动态规划(Dynamic Programming)是解决复杂问题的重要方法,通过将问题分解为重叠子问题并记录中间结果实现高效计算。本文精选六大经典动态规划问题,提供详细的算法解析和C++实现代码。
通过存储已计算结果避免重复计算,时间复杂度从O(2^n)优化到O(n)
状态转移方程
dp[i] = dp[i-1] + dp[i-2]
#include
#include
using namespace std;
int fibonacci(int n) {
if (n <= 1) return n;
int prev = 0, curr = 1;
for (int i = 2; i <= n; ++i) {
int next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
// 测试用例
int main() {
cout << "F(10): " << fibonacci(10) << endl; // 55
cout << "F(20): " << fibonacci(20) << endl; // 6765
return 0;
}
决策物品是否放入背包,二维数组记录状态
状态转移方程
dp[i][w] = max(dp[i-1][w], dp[i-1][w-w_i] + v_i)
#include
#include
#include
using namespace std;
int knapsack(int W, vector<int>& weights, vector<int>& values) {
int n = weights.size();
vector<int> dp(W + 1, 0);
for (int i = 0; i < n; ++i) {
for (int w = W; w >= weights[i]; --w) {
dp[w] = max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[W];
}
// 测试用例
int main() {
vector<int> weights = {2, 3, 4, 5};
vector<int> values = {3, 4, 5, 6};
int capacity = 8;
cout << "Max value: "
<< knapsack(capacity, weights, values) << endl; // 输出11
return 0;
}
二维DP表记录字符串匹配状态,时间复杂度O(nm)
状态转移方程
dp[i][j] =
\begin{cases}
dp[i-1][j-1] + 1, & \text{if } s1[i]==s2[j] \\
max(dp[i-1][j], dp[i][j-1]), & \text{otherwise}
\end{cases}
#include
#include
using namespace std;
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];
}
// 测试用例
int main() {
string s1 = "abcde", s2 = "ace";
cout << "LCS length: "
<< longestCommonSubsequence(s1, s2) << endl; // 输出3
return 0;
}
维护递增序列尾元素数组,时间复杂度O(nlogn)
关键操作
使用二分查找维护最小尾部元素
#include
#include
#include
using namespace std;
int lengthOfLIS(vector<int>& nums) {
vector<int> tails;
for (int num : nums) {
auto it = lower_bound(tails.begin(), tails.end(), num);
if (it == tails.end()) {
tails.push_back(num);
} else {
*it = num;
}
}
return tails.size();
}
// 测试用例
int main() {
vector<int> nums = {10,9,2,5,3,7,101,18};
cout << "LIS length: " << lengthOfLIS(nums) << endl; // 输出4
return 0;
}
计算字符串转换的最小操作次数,时间复杂度O(nm)
状态转移方程
dp[i][j] =
\begin{cases}
dp[i-1][j-1], & \text{if } s1[i]==s2[j] \\
1 + min(insert, delete, replace), & \text{otherwise}
\end{cases}
#include
#include
using namespace std;
int minDistance(string word1, string word2) {
int m = word1.size(), n = word2.size();
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
for (int i = 0; i <= m; ++i) dp[i][0] = i;
for (int j = 0; j <= n; ++j) dp[0][j] = j;
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
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];
}
// 测试用例
int main() {
string s1 = "intention", s2 = "execution";
cout << "Edit distance: "
<< minDistance(s1, s2) << endl; // 输出5
return 0;
}
通过状态转移方程建模交易过程,时间复杂度O(n)
状态定义
• dp0:持有股票的最大收益
• dp1:不持有股票的最大收益
#include
#include
#include
using namespace std;
int maxProfit(vector<int>& prices) {
int dp0 = -prices[0]; // 持有股票
int dp1 = 0; // 不持有股票
for (int i = 1; i < prices.size(); ++i) {
int new_dp0 = max(dp0, dp1 - prices[i]);
int new_dp1 = max(dp1, dp0 + prices[i]);
dp0 = new_dp0;
dp1 = new_dp1;
}
return dp1;
}
// 测试用例
int main() {
vector<int> prices = {7,1,5,3,6,4};
cout << "Max profit: " << maxProfit(prices) << endl; // 输出7
return 0;
}
问题类型 | 时间复杂度 | 空间复杂度 | 典型应用场景 |
---|---|---|---|
斐波那契数列 | O(n) | O(1) | 基础DP练习 |
0-1背包问题 | O(nW) | O(W) | 资源分配问题 |
LCS问题 | O(nm) | O(nm) | 生物信息学、文件差异比较 |
LIS问题 | O(nlogn) | O(n) | 时间序列分析 |
编辑距离 | O(nm) | O(nm) | 自然语言处理 |
股票买卖 | O(n) | O(1) | 金融交易策略 |
本文详细讲解了六种动态规划经典问题及其C++实现,涵盖基础模型到优化技巧。实际应用中需注意:
建议读者在LeetCode等平台练习相关题目(如#70, #1143, #121, #300等),并尝试解决变种问题以加深理解。动态规划的精髓在于通过记录历史状态来避免重复计算,掌握这一思想将大大提高算法设计能力。