01背包
416分割等和子集
这一题,需要能想到把分割等和子集的问题转化为数组中能否凑出
t
a
r
g
e
t
2
\frac{target}{2}
2target。一旦转化了稳提,就变成了一个01背包问题。
内层for循环从右到左,是避免重复使用一个元素。因为从左往右的话,每次利用的前面的数据都是已经处理过了的,而从右到左就不会出现这个问题。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=accumulate(nums.begin(),nums.end(),0);
if(sum&1)
return false;
int target=sum>>1;
//01背包问题
vector<bool>dp(target+1,false);
dp[0]=true;
//用每种物体,对dp数组进行优化
for(auto&n:nums){
for(int i=target;i>=1;i--){
if(i-n>=0 && dp[i-n])
dp[i]=true;
}
}
return dp[target];
}
};
注意accumulate函数是在numeric头文件中的
完全背包
518零钱兑换II
做这一题时,第一遍我做错了,我把他当成最初版本的零钱兑换来写,用一个一维dp,dp[i]+=dp[i-c]
然后发现结果错误。因为比如要凑3块钱,就会出现两种重复的凑法(1,2)和(2,1)。究其原因在于,**没有依次独立地对每种硬币的数量进行选择!**不加顺序地临时选择各种硬币,一定会导致出现重复凑法。
因此,遇到存在两种变量的背包问题时,应该要使用二维dp(视情况可以压缩),对两种变量分别加以分解。并且要考虑是否可以重复使用。
class Solution {
public:
int change(int amount, vector<int>& coins) {
//背包容量为i时,一共有多少种组合方式
vector<int>dp(amount+1,0);
dp[0]=1;
for(auto&c:coins){
//从左到右,意味着可以重复使用硬币
for(int i=1;i<=amount;i++)
if(i-c>=0)
dp[i]+=dp[i-c];
}
return dp[amount];
}
};