完全背包理论基础
完全和01背包的区别:
物品的数量是无数个,因此在01背包中遍历背包时,正序遍历即可,这样就是用的这一行已经修改过的数据,因此正序,而倒序遍历时,左边都是上一行的状态,因此只添加了一次,是01背包
两层for循环可以颠倒(仅限纯完全背包),排列和组合是不一样的
遍历物品在外层循环,遍历背包容量在内层循环,状态如图:
遍历背包容量在外层循环,遍历物品在内层循环,状态如图:
因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了,先行还是先列都是会先算出之前的数据
518. 零钱兑换 II
解题思路
1. dp[j] 装满容量为j的背包,有dp[j]种方法
2.dp[j] += dp[ j- coins[i] ]
3. dp[0] = 1
4.遍历顺序
先遍历物品,再遍历背包,物品2再物品1后面,因此只会出现1,2这种情况,是组合
先背包,再物品,背包的容量增大时,每个物品又会被重新遍历,因此可能会出现2放入之后再放1的情况,也就是排列
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];
}
};
- 时间复杂度: O(mn),其中 m 是amount,n 是 coins 的长度
- 空间复杂度: O(m)
377. 组合总和 Ⅳ
解题思路
与上题没有变化,但是先遍历背包时,要多加一个判断,就是放入的物体要比背包容量小
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1,0);
dp[0] = 1;
for(int j=0 ; j<=target ; j++)
{
for(int i = 0; i<nums.size() ; i++)
{
if(j >= nums[i] && dp[j]< INT_MAX - dp[ j-nums[i] ]) //放入的物品要比背包容量小,并且dpj不会超过int最大值
dp[j] += dp[ j-nums[i] ];
}
}
return dp[target];
}
};
时间复杂度: O(target * n),其中 n 为 nums 的长度
空间复杂度: O(target)
收获
01背包和完全背包的区别
两层for循环的循序与排列或者组合有关,并注意排列需要添加额外的判断条件