C++动态规划dp

背包问题

0-1背包

一维数组0-1背包(滚动数组)

求背包最大容量

先遍历物品i,再遍历背包j。

物品正序,背包倒序(保证物品i只被放进一次)

dp[j]为 容量为j的背包所背的最大价值

vector<int> dp(bagWeight + 1, 0);
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}
return dp[bagWeight];

求装满背包有多少种方法

vector<int> dp(bagSize + 1, 0);
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
    for (int j = bagSize; j >= nums[i]; j--) {
        dp[j] += dp[j - nums[i]];
    }
}
return dp[bagSize];

完全背包

东西可添加多次 -> 从小到大遍历

求背包最大容量

纯完全背包问题,for循环顺序可颠倒。(个人习惯先物品i后背包j)

vector<int> dp(bagWeight + 1, 0);
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

求装满背包有多少种方法(组合的个数)

先遍历物品i,再遍历背包j。

物品正序,背包倒序。

int change(int amount, vector<int>& coins) {
    vector<int> dp(amount + 1, 0);
    dp[0] = 1;
    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        for (int j = coins[i]; j <= amount; j++) { // 遍历背包
            dp[j] += dp[j - coins[i]];
        }
    }
    return dp[amount];
}

(排列的个数)

先遍历背包j,再遍历物品i。

都正序。

for (int j = 0; j <= amount; j++) { // 遍历背包容量
    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
    }
}

凑满背包最少有多少物品

vector<int> dp(amount + 1, INT_MAX);
dp[0] = 0;
for (int i = 0; i < coins.size(); i++) { // 遍历物品
    for (int j = coins[i]; j <= amount; j++) { // 遍历背包
        if (dp[j - coins[i]] != INT_MAX) { // 如果dp[j - coins[i]]是初始值则跳过
            dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
        }
    }
}

买卖股票

使用n * 2的二维数组。n表示天数,2表示是否持有股票的状态。

int maxProfit(vector<int>& prices) {
    if (prices.size() == 0) return 0;
    vector<vector<int>> dp(prices.size(),vector<int>(5, 0));
    dp[0][1] = -prices[0];
    dp[0][3] = -prices[0];
    for (int i = 1; i < prices.size(); i++) {
        dp[i][0] = dp[i - 1][0];
        dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
        dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
        dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
        dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
    }
    return dp[prices.size() - 1][4];
}

最长子序列

最长上升子序列

dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度。

vector<int> dp(nums.size(), 1);
int result = 0;
for (int i = 1; i < nums.size(); i++) {
    for (int j = 0; j < i; j++) {
        // 位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
        if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1); // 
    }
    if (dp[i] > result) result = dp[i]; // 取长的子序列
}
return result;

最长重复子数组(连着的)

两个数组里的 一般都是定义 “以i - 1为结尾的”

dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。

(特别注意: “以下标i - 1为结尾的A” 标明一定是 以A[i-1]为结尾的字符串 )

vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
for (int i = 1; i <= nums1.size(); i++) {
    for (int j = 1; j <= nums2.size(); j++) {
        if (nums1[i - 1] == nums2[j - 1]) {
            dp[i][j] = dp[i - 1][j - 1] + 1;
        }
        if (dp[i][j] > result) result = dp[i][j];
    }
}
return result;

最长公共子序列(不连着的)

dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]。

主要就是两大情况: text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同

如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1;

如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。

即:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);

vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
for (int i = 1; i <= text1.size(); i++) {
    for (int j = 1; j <= text2.size(); 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[text1.size()][text2.size()];
<think>嗯,用户想了解如何在C++中使用动态规划DP)。我需要先回顾一下动态规划的基本概念,然后结合C++的具体实现方法。首先,动态规划通常用于解决具有重叠子问题和最优子结构的问题。比如斐波那契数列、背包问题这些经典例子。 接下来,我得考虑如何在C++中实现动态规划。一般来说,动态规划有两种方法:自顶向下的记忆化搜索和自底向上的递推。对于自顶向下,C++中可以使用递归配合备忘录,比如用数组或哈希表存储已计算的结果。不过递归可能会有栈溢出的风险,需要注意问题规模。自底向上则是通过循环迭代,从基础情况开始逐步构建解,这种方法通常更高效,空间复杂度也可能优化得更好。 用户可能需要具体的步骤和示例代码。比如,以斐波那契数列为例,自顶向下的方法可以用递归加备忘录,而自底向上可以用数组迭代。此外,还可以进一步优化空间,比如只用两个变量来存储前两个状态,而不是整个数组。 另外,用户可能想知道动态规划的应用场景和设计步骤。需要说明如何定义状态、状态转移方程、初始条件和边界情况。例如,在背包问题中,状态可能是dp[i][j]表示前i个物品在容量j时的最大价值,状态转移方程则根据是否选择当前物品来更新。 还要注意C++中的实现细节,比如数组的大小、循环的起始和终止条件,以及如何初始化数组。比如,在自底向上的斐波那契中,需要先处理初始的dp[0]和dp[1],然后从2开始循环到n。 可能用户会遇到的问题包括如何选择递推方向、空间优化的方法,以及如何处理更复杂的状态转移。需要提醒用户注意数组越界、重复计算和正确性验证。比如,使用记忆化搜索时要确保所有可能的状态都被正确存储和检索。 总结一下,我应该分步骤介绍动态规划的概念,两种实现方法,给出具体的C++代码示例,并讨论优化技巧和常见问题。这样用户能够逐步理解并应用动态规划解决实际问题。</think>动态规划DP)是一种通过将复杂问题分解为重叠子问题来优化求解的方法。以下是在C++中实现动态规划的详细步骤和示例: --- ### 一、动态规划核心要素 1. **定义状态** 确定问题中的变量,用`dp[...]`表示状态,例如: - 斐波那契数列:`dp[i]`表示第`i`项的值 - 背包问题:`dp[i][j]`表示前`i`个物品在容量`j`时的最大价值 2. **状态转移方程** 建立子问题之间的关系,例如: $$dp[i] = dp[i-1] + dp[i-2]$$ $$dp[i][j] = \max(dp[i-1][j], dp[i-1][j-w_i] + v_i)$$ 3. **初始化和边界条件** 设置初始值(如`dp[0] = 0`),并处理越界情况。 --- ### 二、C++实现方法 #### 方法1:自底向上(迭代) **示例:斐波那契数列** ```cpp int fib(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:自顶向下(记忆化搜索) **示例:斐波那契数列** ```cpp #include <vector> using namespace std; int helper(int n, vector<int>& memo) { if (n <= 1) return n; if (memo[n] != -1) return memo[n]; memo[n] = helper(n-1, memo) + helper(n-2, memo); return memo[n]; } int fib(int n) { vector<int> memo(n+1, -1); return helper(n, memo); } ``` #### 方法3:空间优化(滚动数组) ```cpp int fib(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; } ``` --- ### 三、经典问题实战:0-1背包 **问题描述** 容量为`W`的背包,`n`个物品,第`i`个物品重量`w[i]`、价值`v[i]`,求最大价值。 **C++代码** ```cpp int knapsack(int W, vector<int>& w, vector<int>& v) { int n = w.size(); vector<vector<int>> dp(n+1, vector<int>(W+1, 0)); for (int i = 1; i <= n; i++) { for (int j = 1; j <= W; j++) { if (j >= w[i-1]) { dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1]); } else { dp[i][j] = dp[i-1][j]; } } } return dp[n][W]; } ``` --- ### 四、关键技巧 1. **状态压缩**:若当前状态仅依赖前一行,可将二维数组优化为一维数组。 2. **遍历顺序**:完全背包需**正序**遍历,0-1背包需**逆序**遍历。 3. **调试技巧**:打印DP表,验证每一步的转移逻辑。 --- ### 五、常见错误 1. 未处理边界条件(如`dp[0]`初始化错误) 2. 状态转移方程与问题实际逻辑不匹配 3. 空间复杂度未优化导致内存超限 通过以上步骤和示例,可以逐步掌握动态规划C++中的实现方法。建议从简单问题(如爬楼梯、最长递增子序列)开始练习,逐步过渡到复杂场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值