代码随想录算法训练营第三十九天|01背包问题 二维、 01背包问题 一维、416. 分割等和子集
背包问题
01背包、完全背包、多重背包主要区别在于物品数量:01背包每个物品只有一个,完全背包物品有无限个,多重背包每个物品数量不同。
dp[i][j]表示编号[0,i]的物品任取放入容量为j的背包后,背包里物品的最大价值。
01背包问题 二维
#include <iostream>
#include <vector>
using namespace std;
int bag(vector<int>& space, vector<int>& value, int m, int n){
// dp[i][j]表示在[0,i]物品任选,装入容量为j的背包的最大价值
vector<vector<int>> dp(m, vector<int>(n+1));
for(int j=space[0];j<=n;j++){
dp[0][j] = value[0];// 初始化将物品0装入背包
}
for(int i=1;i<m;i++){
for(int j=0;j<=n;j++){
if(j<space[i]){
// 容量不够装入第i个物品
dp[i][j] = dp[i-1][j];
}else{
// 装入i vs 不装入i
dp[i][j] = max(dp[i-1][j], dp[i-1][j-space[i]]+value[i]);
}
}
}
return dp[m-1][n];
}
int main(){
int m, n;
cin>>m>>n;
vector<int> space(m);
vector<int> value(m);
for(int i = 0;i<m;i++){
cin>>space[i];
}
for(int i = 0;i<m;i++){
cin>>value[i];
}
int res;
res = bag(space, value, m, n);
cout<< res <<endl;
return 0;
}
①dp[i][j]表示在[0,i]物品中任选,装入容量为j的背包中获得的最大价值。
②对于每次选择只会存在两种结果:一是选择将物品i放入背包,二是将物品i不放入背包。放不放物品是根据价值来判断,因此,递推公式:dp[i][j]=max(dp[i-1][j], dp[i-1][j-space[i]]+value[i]),其中dp[i-1][j]表示不放入物品i,dp[i-1][j-space[i]]+value[i]表示放入物品i,j-space[j]表示腾出放入i的空间。
③初始化仅放入物品0的情况,只要容量大于物品0需要的空间,就选择放入物品0。
④遍历顺序从物品0遍历到物品m-1,背包容量从0到n。
01背包问题 一维
int bag(vector<int>& space, vector<int>& value, int m, int n){
// dp[j]表示装入容量为j的背包的最大价值
vector<int> dp(n+1);
for(int i=0;i<m;i++){
for(int j=n;j>=space[i];j--){
// 装入i vs 不装入i
dp[j] = max(dp[j], dp[j-space[i]]+value[i]);
}
}
return dp[n];
}
一维背包主要是在之前二维背包的基础上,将二维dp数组压缩成一维dp数组,在二维dp数组的递推公式中,dp[i][j]里,i都依赖与i-1的值,因此可以将i-1的值都拷贝到i,以此压缩数组。基本思路如下:
①dp[j]表示容量为j的背包可装的最大价值。
②与二维类似,存在两种选择,放入物品和不放入物品。不放入物品的最大价值是:dp[j],不放入物品:dp[j-space[i]]+value[i]。
③初始化:背包容量为0的时候,dp[0]=0。
④遍历顺序需要从后往前遍历。假设背包容量5,物品3种,space=[1,3,4],value=[15,20,30]。对于物品0,如果正序遍历:
dp[1] = dp[1 - weight[0]] + value[0] = dp[0] + 15 = 15
dp[2] = dp[2 - weight[0]] + value[0] = dp[1] + 15 = 30
此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。如果倒序遍历:
dp[2] = dp[2 - weight[0]] + value[0] = dp[1] + 15
dp[1] = dp[1 - weight[0]] + value[0] = dp[0] + 15
所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。
416. 分割等和子集(中等)
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum % 2) {
return false;
}
// 相当于背包容量为sum/2
vector<int> dp(sum / 2 + 1);
for (int i = 0; i < nums.size(); i++) {
for (int j = sum / 2; j >= nums[i]; j--) {
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
if (dp[sum / 2] == sum / 2)
return true;
else
return false;
}
};
本题难度中等。本题为01背包的应用,将数组分为两个相等的子集,先计算sum,可以看作背包容量为sum/2,需要使用nums的数字装满背包,且数字的价值和重量相同。最后判断一下容量为sum/2所装最大价值是不是sum/2。