1.完全背包
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
1.针对01背包问题的一维数组,其实每次的遍历背包都是倒叙遍历的,这是为了让分开上下层环,以便能得到不重复出现的最优解。
2.但是完全背包不同,它可以出现重复的物件,那么我们不需要考虑倒叙上下层关系,直接正序遍历就可以满足可出现重复的条件
改写01背包一维数组遍历模式就能得到我们需要的完全背包算法
for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);} }
那么关于遍历的顺序问题:
其实我们能发现,这个算法的思想就是由物件和背包的上个状态推出当前的状态(注意,这里说的上一次值得就是背包容量+1或者物件+1的状态,而不是01背包的上一次循环的概念),那么我们其实是知道的,所谓的上一次推出下一次,就是数组的左上角的值不断推出右下角的值。换句话说,不管哪个顺序,二维数组中都是左上角推出右下角,那么无所谓哪个条件先遍历。
// 先遍历物品,在遍历背包 void test_CompletePack(vector<int>& weight,vector<int>* value, int& bagWeight) {vector<int> dp(bagWeight + 1, 0);for (int i = 0; i < weight.size(); i++) // 遍历物品{for (int j = weight[i]; j <= bagWeight; j++) // 遍历背包容量{dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}} }// 先遍历背包,再遍历物品 void test_CompletePack(vector<int>& weight, vector<int>* value, int& bagWeight) {vector<int> dp(bagWeight + 1, 0);for (int j = 0; j <= bagWeight; j++) // 遍历背包容量{for (int i = 0; i < weight.size(); i++) // 遍历物品{if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}} }
2.零钱兑换 II
518. 零钱兑换 II
本题与494. 目标和思路基本一致
1.dp[j]:背包j容量所能达到要求的种类个数
2.dp[i]为i容量下的种类个数,其实有i种可能:当质量为1的物品放进去dp[i-1]有几种,当质量为2的物品放进去dp[i-2]有几种...当质量为i-1的物品放进去dp[1]有几种,这些情况相加。那么我们知道所谓的条件就是:dp[j] += dp[j - nums[i]];
3.不过,此题不需要考虑数据的重复出现问题,也就是说,本题是一道完全背包问题。
4.初始化,dp[0]=1;因为dp[1]条件是dp[0]推出来的,如果dp[0]为0,后面都会变为0。
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];}
};
3.组合总和 Ⅳ
377. 组合总和 Ⅳ
1.本题与上一题最大的区别就是:上一题是组合问题,而这题为排列问题
2.解决方法就是本题为背包先遍历,物件后遍历
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]>=0&&dp[j]<INT_MAX-dp[j-nums[i]])dp[j]+=dp[j-nums[i]];}}return dp[target];}
};
4.关于上面两题引出的排列和组合问题
之所以出现这个问题,是因为完全背包不需要考虑物件重复的问题,那么自然就分出是否要考虑物价的存入先后问题。上两题的核心代码都是dp[j]+=dp[j-nums[i]],只不过排列先遍历背包,组合先遍历物件。
为什么这样呢?
其实很好思考:
1.对于组合,先遍历物件,那么外面的循环会拿到1物品,然后依次判断是否满足背包的容量。下一层外面的循环会拿到2物品,依次遍历背包是,天然的一个条件就是1物件要么一定不放,要么已经放进去,那么我们不会再往回走外层循环得到1物件重新放入的机会,其结果就是要么不放1物件,要放就放在2物件的前面,顺序都定死只有唯一的一种,那么结果自然是组合
2.对于排列,先遍历背包,那么外面的循环会先按照1背包来跟所有物件衡量大小,1物件有可能能被放入,2物件也有可能能被放入。那下一次背包2遍历,背包2继承了背包1的一部分,背包2也依次跟所有物件衡量大小,那么就出现了1物件放在2物件的后面的可能