今天主要是完全背包的一个专题,完全背包相对于 01 背包,主要区别在于物品可以取出无限次,其他关于 dp 的状态定义和状态转移都是一样的,体现在代码上,最重要的是遍历的一个先后顺序以及遍历开始的位置。
一、题目打卡
1.1 零钱兑换
题目链接:. - 力扣(LeetCode)
class Solution {
public:int change(int amount, vector<int>& coins) {vector<int> dp(amount+1,0); // 用 i 种硬币可以组成 amont 的数量dp[0] = 1;for(int i = 0;i < coins.size();i++){// for(int it : dp){// cout << it << " ";// }// cout << endl;for(int j = coins[i];j <= amount;j++){dp[j] = dp[j] + dp[j-coins[i]];}// cout << "----------------" << endl;// for(int it : dp){// cout << it << " ";// }// cout << endl;// cout << "----------------" << endl;}return dp[amount];}
};
这里需要注意一件事,就是遍历的时候,因为这里是一维数组,所以遍历的时候需要从物品开始,原因是一维数组无法存储上一次遍历过的信息,具体的过程看到一个评论觉得写的很好:
这里需要注意,不管遍历的顺序如何,dp 所开辟的大小和状态的定义都是不变的,从上面这个图其实也可以看出来,如果先遍历的是背包的容量的话,最终就会变成一个组合问题,而事实上,对于选择物品来说,并不存在顺序上的不同,所以这样是不可取的,而事实上,对于最原始的完全背包问题,遍历的顺序是没有关系的,因为原始的完全背包问题,计算的是最大的物品价值,这时候和顺序也是没有关系的,而且还有一点值得注意,那就是只有一维数组写的时候才会存在这样遍历的问题,而对于二维的矩阵,是不存在这个问题的,因为不存在数组的覆盖。
1.2 组合总和IV
题目链接:. - 力扣(LeetCode)
class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<double> dp(target+1,0);dp[0] = 1;// for(int i = 0 ; i < nums.size();i++){// for(int j = nums[i] ; j <= target ; j++){// dp[j] += dp[j - nums[i]];// }// }for(int j = 0 ; j <= target ; j++){for(int i = 0 ; i < nums.size();i++){// if(nums[i] <= j && dp[j] + dp[j - nums[i]] < INT_MAX) dp[j] += dp[j - nums[i]];// 这个判断条件如果double ,第二个可以省略,而且就算是int 这个也不对,看我解析if(nums[i] <= j && dp[j] + dp[j - nums[i]] < INT_MAX) dp[j] += dp[j - nums[i]];}}return dp[target];}
};
这个题就是上面那种情况,因为需要求的是组合的数量,所以需要先遍历背包的容量,这个题目还有一个要注意的是越界的问题,我这里直接将数定义为了 double 类型,或者是在哪个条件里面,可以写 dp[j] < INT_MAX - dp[j - nums[i]] ,注意我那个代码里面那样是不对的,因为这样会先计算和,这样如果越界了还是会报错,正确的应该上面那一句的写法,这里就当警示自己了。