当前位置: 首页 > article >正文

7.DP算法

DP

在C++中,动态规划(Dynamic Programming,DP)是一种通过将复杂问题分解为重叠子问题来高效求解的算法设计范式。以下是DP算法的核心要点和实现方法:


一、动态规划的核心思想

  1. 重叠子问题:问题可分解为多个重复的子问题(避免重复计算)
  2. 最优子结构:问题的最优解包含子问题的最优解
  3. 状态转移方程:定义如何从子问题的解推导出原问题的解

二、动态规划的典型实现步骤

  1. 定义状态
    明确dp[i]dp[i][j]的含义(如dp[i]表示前i个元素的最优解)
  2. 初始化状态
    设置初始条件(如dp[0] = 0
  3. 推导状态转移方程
    建立递推关系(如dp[i] = max(dp[i-1], ...))
  4. 确定遍历顺序
    确保计算当前状态时所需的前置状态已被计算
  5. 处理结果
    从最终状态或中间状态提取答案

三、经典问题与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];
}

四、优化技巧

  1. 状态压缩:将二维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];
    }
    
  2. 备忘录法:递归+缓存(自顶向下)

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

五、注意事项

  1. 避免状态定义模糊:明确每个维度代表的具体含义
  2. 边界条件处理:如数组索引越界、初始值设置错误
  3. 空间优化陷阱:状态压缩时注意遍历顺序(如背包问题必须逆向遍历)
  4. 避免过度优化:先保证正确性,再考虑优化

六、典型应用场景

问题类型示例问题
线性DP最大子数组和、爬楼梯
区间DP矩阵链乘法、回文分割
树形DP二叉树中的最大路径和
状态机DP股票买卖系列问题
位运算DP旅行商问题(TSP)

七、调试技巧

  1. 打印DP表格观察状态变化
  2. 小规模测试用例验证
  3. 对比暴力递归与DP解法结果

动态规划的难点在于找到正确的状态定义推导状态转移方程。建议通过经典问题(如背包、LCS、编辑距离等)积累经验,逐步掌握这一强大的算法范式。


http://www.kler.cn/a/529865.html

相关文章:

  • gesp(C++六级)(10)洛谷:P10722:[GESP202406 六级] 二叉树
  • C# Winform enter键怎么去关联button
  • 【自然语言处理(NLP)】基于Transformer架构的预训练语言模型:BERT 训练之数据集处理、训练代码实现
  • 使用MATLAB进行雷达数据采集可视化
  • 比较热门的嵌入式项目
  • < OS 有关 > 阿里云 几个小时前 使用密钥替换 SSH 密码认证后, 发现主机正在被“攻击” 分析与应对
  • LabVIEW如何高频采集温度数据?
  • lstm预测
  • 解密高性能异步I/O:io_uring的魔力与应用
  • 理解知识蒸馏中的散度损失函数(KLDivergence/kldivloss )-以DeepSeek为例
  • scrape登录(js逆向)
  • 负载均衡器高可用部署
  • 【数据结构】_链表经典算法OJ:链表判环问题
  • C#面试常考随笔9:什么是闭包?
  • C++泛型编程指南04-(对默认调用参数的类型推断)
  • 最新码支付个人免签支付系统源码 三网免挂版本 兼容易支付
  • 【数据结构】_链表经典算法OJ:相交链表
  • linux中统计文件中特定单词或字符串的出现次数
  • CMake项目编译与开源项目目录结构
  • 面试常考题目——状态码总结
  • 96,【4】 buuctf web [BJDCTF2020]EzPHP
  • JavaFX - 事件处理
  • Mac上的虚拟化软件推荐
  • Go 中 defer 的机制
  • 基于开源AI智能名片2 + 1链动模式S2B2C商城小程序源码在抖音招商加盟中的应用与创新
  • web前端13--动画