day44
完全背包理论基础
每个物品可以使用无限次
// 0 1背包核心代码
for( int i = 0; i < weight.size(); i++){ //遍历物品
for( int j = bagweight; j >= weight[i]; j--){ //遍历背包容量 从大到小遍历
dp[j] = max( dp[j] , dp[j-weigth[i]]+value[i]);
}
}
// 完全背包核心代码
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]);
}
}
同时物品和背包的遍历顺序是可以颠倒的,然而这是对于纯完全背包问题,具体问题需要具体确定遍历顺序
完全背包问题的二维dp数组实现:
// chatgpt生成结果
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m; // n表示物品数量,m表示背包容量
// 初始化物品体积和价值
vector<int> w(n + 1), v(n + 1);
for (int i = 1; i <= n; i++) {
cin >> w[i] >> v[i];
}
// 初始化DP数组
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
// 状态转移方程
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (j >= w[i]) {
dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i]);
}
else {
dp[i][j] = dp[i - 1][j];
}
}
}
// 输出最大价值
cout << dp[n][m] << endl;
return 0;
}
// 修改之前01背包的方法,需要改变初始化时候的值即可
for (int j = weight[0]; j <= bagweight; j++) { //行初始化为对应的值
dp[0][j] = values[0]*(j/weight[0]); //二维背包第一行初始化和之前的不同
//dp[0][j] = values[0];
} //其余都相同
518.零钱兑换II
题目链接:518. 零钱兑换 II
完全背包:有背包和物品,每个物品可以被取无限次
凑成总金额的组合数而非排列数
动态规划五部曲:
1.确定dp[j]及下标的含义:凑成总金额为j的货币组合数为dp[j]
2.确定递推公式:dp[j] += dp[j-coins[i]]; 和目标和中的递归公式一样
求装满背包有几种方法的递推公式都是$dp[j] += dp[j-coins[i]]$
3.dp数组初始化:dp[0] = 1; 如果为0的话之后所有的推导就都为0了,其余都为0;
4.确定遍历顺序:
先遍历物品再遍历背包就是组合问题,先遍历背包再遍历物品就是排列问题
for( int i = 0; i < coins.size(); i++){ //遍历物品 for( int j = coins[i]; j <= amount; j++){ //遍历背包 dp[j] += dp[j - coins[i]]; } } // for循环颠倒顺序就是排列数
5.打印dp数组
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
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];
}
};
int main() {
int amount,n;
cout << "输入总金额和硬币数:";
cin >> amount >> n;
vector<int> coins(n);
cout << "输入每个硬币的价值:";
for (int i = 0; i < n; i++) cin >> coins[i];
Solution solution;
int test = solution.change(amount, coins);
cout << "组合数为:";
cout << test << endl;
return 0;
}
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
377.组合总和IV
解题链接:377. 组合总和 Ⅳ
这里的组合相当于排列问题,可以抽象为之前的爬楼梯的问题,所以在循环的时候需要考虑遍历顺序,先遍历背包再遍历物品。
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1,0);
dp[0] = 1;
// for( int i = 0; i < nums.size(); i++){
// for( int j = nums[i]; j <= target; j++){
// dp[j] += dp[j - nums[i]];
// }
// }
for( int j = 0; j <= target; j++){
for( int i = 0; i < nums.size(); i++){
if( j - nums[i] >= 0 && dp[j] < INT_MAX - dp[j - nums[i]])
dp[j] += dp[j-nums[i]];
}
}
return dp[target];
}
};
求装满背包有几种方法,递归公式都是一样的,没有什么差别,但关键在于遍历顺序!