动态规划经典算法详解与C++实现

动态规划经典算法详解与C++实现

动态规划(Dynamic Programming)是解决复杂问题的重要方法,通过将问题分解为重叠子问题并记录中间结果实现高效计算。本文精选六大经典动态规划问题,提供详细的算法解析和C++实现代码。

一、斐波那契数列(基础入门)

算法原理

通过存储已计算结果避免重复计算,时间复杂度从O(2^n)优化到O(n)

状态转移方程
dp[i] = dp[i-1] + dp[i-2]

C++实现

#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;
}

二、0-1背包问题(经典模型)

算法原理

决策物品是否放入背包,二维数组记录状态

状态转移方程
dp[i][w] = max(dp[i-1][w], dp[i-1][w-w_i] + v_i)

C++实现(空间优化版)

#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;
}

三、最长公共子序列(LCS)

算法原理

二维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}

C++实现

#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;
}

四、最长递增子序列(LIS)

算法原理

维护递增序列尾元素数组,时间复杂度O(nlogn)

关键操作
使用二分查找维护最小尾部元素

C++实现(优化版)

#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}

C++实现

#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:不持有股票的最大收益

C++实现(通用解法)

#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) 金融交易策略

动态规划解题四步法

  1. 定义状态:明确dp数组的含义
  2. 状态转移:建立递推关系式
  3. 初始化:设置边界条件
  4. 计算顺序:确定遍历方向

优化技巧

  1. 状态压缩:滚动数组减少空间复杂度(如斐波那契数列)
  2. 记忆化搜索:递归+缓存实现DP
  3. 贪心优化:结合贪心思想减少状态数(如LIS问题)
  4. 数学分析:寻找问题数学规律(如背包问题)

总结

本文详细讲解了六种动态规划经典问题及其C++实现,涵盖基础模型到优化技巧。实际应用中需注意:

  1. 仔细分析问题是否满足最优子结构
  2. 避免过度设计,优先考虑简单状态定义
  3. 通过打印DP表调试复杂问题
  4. 结合实际问题进行空间优化

建议读者在LeetCode等平台练习相关题目(如#70, #1143, #121, #300等),并尝试解决变种问题以加深理解。动态规划的精髓在于通过记录历史状态来避免重复计算,掌握这一思想将大大提高算法设计能力。

你可能感兴趣的:(算法,算法,动态规划,c++)