完全背包理论基础
视频讲解: https://www.bilibili.com/video/BV1uK411o7c9https://programmercarl.com/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85.html
完全和01背包的区别:
物品的数量是无数个,因此在01背包中遍历背包时,正序遍历即可,这样就是用的这一行已经修改过的数据,因此正序,而倒序遍历时,左边都是上一行的状态,因此只添加了一次,是01背包
两层for循环可以颠倒(仅限纯完全背包),排列和组合是不一样的
遍历物品在外层循环,遍历背包容量在内层循环,状态如图:
遍历背包容量在外层循环,遍历物品在内层循环,状态如图:
因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了,先行还是先列都是会先算出之前的数据
518. 零钱兑换 II
视频讲解: 动态规划之完全背包,装满背包有多少种方法?组合与排列有讲究!| LeetCode:518.零钱兑换II_哔哩哔哩_bilibili代码随想录
解题思路
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. 组合总和 Ⅳ
视频讲解: 动态规划之完全背包,装满背包有几种方法?求排列数?| LeetCode:377.组合总和IV_哔哩哔哩_bilibili代码随想录
解题思路
与上题没有变化,但是先遍历背包时,要多加一个判断,就是放入的物体要比背包容量小
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循环的循序与排列或者组合有关,并注意排列需要添加额外的判断条件