代码随想录第四十四天|完全背包、Leetcode518. 零钱兑换 II、Leetcode377. 组合总和 Ⅳ
完全背包
文章链接:完全背包
与01背包问题唯一的不同点,就在于完全背包的货物可以重复使用,表现在应用中就是同一数据可以复用。而表现在代码中,就是遍历顺序的改变:
01背包外层for循环必须为物品,内层for循环必须为倒序遍历背包容量,原因是:
- 递推公式为
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
,如果外层为背包容量,那么dp[j]一开始都是0,递推公式就变成了dp[j] = max(dp[j], 0+ value[i]);
,最后只会放进一个价值最高的物品,而不是组合起来的一组物品; - 而倒序是因为
dp[j]
的更新过程会用到上一轮dp[j - weight[i]]
的数据,如果把前置位的数据先更新(覆盖),那么上一轮遍历的数据就被覆盖掉了,还用啥去。
完全背包就不讲究内外层循环了,只有正序遍历背包容量这一条,原因是:
- 因为其正序遍历的特性,你遍历到dp[j]的时候,dp[j-xxx]一定是遍历过了,有值的,所以就无所谓了
- 而正序举个例子就懂了,物品0的重量weight[0] = 1,价值value[0] = 15,
dp[1] = dp[1 - weight[0]] + value[0] = 15
dp[2] = dp[2 - weight[0]] + value[0] = dp[1] + value[0] = 30
可以发现遍历容量2的时候套用了容量1的最优解,把0用了两遍,所以只有数据可以复用时能正序
这玩意挺绕,昨天刚学会今天又忘了 ,所以记住01背包外层物品内层倒序容量,完全背包正序遍历背包容量,就得了。。。
Leetcode518. 零钱兑换 II
题目链接:Leetcode518. 零钱兑换 II
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> dp(amount+1,0);//dp[j]代表总金额为j,的最大方案数
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];
}
};
要记得组合问题的递推公式是dp[j]+=dp[j-nums[i]];
,且dp[0]必须初始化为1
Leetcode377. 组合总和 Ⅳ
题目链接:Leetcode377. 组合总和 Ⅳ
这个还不错,自己想出来怎么改了
唯独对一个测试用例的判断有点问题,因为力扣给了个两数相加超过INT_MAX的用例,所以加上判断dp[j]<INT_MAX-dp[j-nums[i]]
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1,0);
dp[0]=1;
for(int j=1;j<=target;j++){
for(int i=0;i<nums.size();i++){
if(j>=nums[i]&&dp[j]<INT_MAX-dp[j-nums[i]]) dp[j]+=dp[j-nums[i]];
}
}
return dp[target];
}
};