c++动态规划(超详细,全是干货!)

C++中的动态规划(Dynamic Programming, DP)是一种通过分解问题、存储中间结果,从而减少重复计算、提高效率的算法技巧。DP主要适用于那些可以通过“子问题”的解逐步构建出“大问题”解的问题。下面将详细讲解动态规划的基本概念、实现步骤、常见问题类型和一些典型的例题。

动态规划的基本概念

动态规划通常用于优化解决以下类型的问题:

  1. 重叠子问题:问题可以分解为多个子问题,且这些子问题会被多次计算。
  2. 最优子结构:问题的最优解可以通过子问题的最优解来构建。

为了避免重复计算,动态规划会使用一个数组或表格存储已经计算过的子问题的解,从而实现“记忆化”效果,降低时间复杂度。

动态规划的实现步骤

动态规划的解题一般遵循以下步骤:

  1. 定义状态:设定一个状态表示。例如,对于一个求最短路径的问题,可以定义dp[i]表示到节点i的最短路径长度。

  2. 状态转移方程:找出当前状态与之前状态之间的关系。这是动态规划的核心。比如,假设dp[i]的值可以通过dp[i-1]dp[i-2]计算得出,那么状态转移方程可以表示为:dp[i] = min(dp[i-1], dp[i-2]) + cost[i]

  3. 初始化:根据题意初始化dp数组的值。例如,若dp[0]表示开始状态,它的值可以设为0或其他值。

  4. 计算并获取结果:依据状态转移方程逐步填充dp数组,最终的解通常会在dp[n]dp[n-1]等位置得到。

动态规划的题目类型

动态规划可以应用于各种类型的问题,以下是几类常见的动态规划题目类型:

  1. 线性动态规划

    • 斐波那契数列:求斐波那契数列的第n项。
    • 爬楼梯问题:有n阶楼梯,每次可以爬1阶或2阶,问有多少种方法爬到顶层。
    • 最小路径和:在一个二维数组中,从左上角到右下角的路径,使路径上的数值和最小。
  2. 区间动态规划

    • 最长回文子串:在给定字符串中,找到长度最长的回文子串。
    • 戳气球问题:在一系列气球中戳破气球,得到不同分数,目标是使总分最大。
  3. 背包问题

    • 0/1背包问题:有一系列物品和一个容量固定的背包,每个物品有重量和价值,问如何选择物品使得总价值最大。
    • 完全背包问题:物品数量不限,可以多次选择某个物品,但不能超过背包容量。
  4. 序列型动态规划

    • 最长上升子序列:在一个数组中找到最长的递增子序列的长度。
    • 编辑距离:给定两个字符串,计算将一个字符串转换成另一个字符串所需的最少编辑操作次数(插入、删除、替换)。
  5. 树形动态规划

    • 树的直径:在一棵树中,找到两点间的最长路径长度。
    • 选点覆盖问题:在树结构中选择尽可能少的点,使得树的每条边至少有一个端点被选中。
  6. 数位动态规划

    • 数位统计:给定一个数范围,统计符合某些条件的数的个数,例如数位上不含4的数。
  7. 状态压缩动态规划

    • 旅行商问题(TSP):有若干城市,求解从某个城市出发访问所有城市并返回的最短路径。
    • 集合划分问题:将集合划分为若干个子集,使得每个子集的某些属性满足条件。

经典例题讲解

1. 斐波那契数列

求解第n项的斐波那契数。斐波那契数列的状态转移方程是:
[ dp[i] = dp[i-1] + dp[i-2] ]
其中dp[0] = 0dp[1] = 1

代码实现

int fibonacci(int n) {
    if (n <= 1) return n;
    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. 爬楼梯问题

假设有n阶楼梯,每次可以爬1阶或2阶,问有多少种不同的方式爬到顶层。

状态定义dp[i]表示到达第i阶楼梯的方法数。
状态转移方程dp[i] = dp[i-1] + dp[i-2]
初始化dp[0] = 1, dp[1] = 1

代码实现

int climbStairs(int n) {
    int dp[n + 1];
    dp[0] = 1;
    dp[1] = 1;
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}
3. 0/1 背包问题

给定一个容量为W的背包和n个物品,每个物品的重量为weight[i],价值为value[i]。问如何选择物品使得在不超过背包容量的情况下总价值最大。

状态定义dp[i][w]表示前i个物品在容量w下的最大价值。
状态转移方程

  • 若不选择第i个物品:dp[i][w] = dp[i-1][w]
  • 若选择第i个物品:dp[i][w] = max(dp[i-1][w], dp[i-1][w - weight[i]] + value[i])

代码实现

int knapsack(int W, vector<int>& weight, vector<int>& value) {
    int n = weight.size();
    vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));
    for (int i = 1; i <= n; i++) {
        for (int w = 0; w <= W; w++) {
            if (weight[i - 1] <= w)
                dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight[i - 1]] + value[i - 1]);
            else
                dp[i][w] = dp[i - 1][w];
        }
    }
    return dp[n][W];
}

总结

C++中的动态规划是一种将问题分解、存储计算结果的优化方法。它的精髓在于通过状态转移方程逐步构建解,使得算法时间复杂度大大降低。在学习和应用动态规划时,理解题目类型和状态转移方程是关键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wu_joey

创作不易,鼓励一下吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值