完全背包
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
01背包问题在求解时为了避免物品被多次添加选择了从大到小遍历背包,而完全背包由于一个物品可以被添加无数次,所以需要从小到大遍历
01背包遍历代码:
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
完全背包遍历代码:
// 先遍历物品,再遍历背包
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]);
}
}
而且对于完全背包来说,先遍历物品或先遍历背包都不影响最终结果。
完全背包完整的测试代码-java
//先遍历物品,再遍历背包
private static void testCompletePack(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.length; i++){ // 遍历物品
for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
//先遍历背包,再遍历物品
private static void testCompletePackAnotherWay(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量
for (int j = 0; j < weight.length; j++){ // 遍历物品
if (i - weight[j] >= 0){
dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
}
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
518. 零钱兑换 II
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
解题思路
本题是一个完全背包问题的变形,区别在于要将求背包最大价值变为求组合总数。仍然按照动态规划的五步分析法来解答:
确定dp数组的含义
dp[j] :表示总金额为j的硬币组合数为dp[j]
确定递推公式
求解组合问题的递推公式与 494.目标和一样
所以递推公式为:
dp[j] += dp[j-coins[i]]
初始化
dp[0] =1 ,为了确保后续的推导不为0.
确定遍历顺序
此题是为了求组合数,所以采用外层遍历物品,内层遍历背包的方法
自行带入验证递推公式
略过
代码示例-java
class Solution {
public int change(int amount, int[] coins) {
// 定义数组
int[] dp = new int[amount+1];
// 初始化
dp[0] = 1;
// 遍历
for(int i=0;i<coins.length;i++){
for(int j=coins[i];j<=amount;j++){
dp[j] += dp[j-coins[i]];
}
}
return dp[amount];
}
}
377. 组合总和 Ⅳ
给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
示例:
nums = [1, 2, 3]
target = 4
所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1)
请注意,顺序不同的序列被视作不同的组合。
因此输出为 7。
解题思路
本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,其实就是求排列!
那么遍历时应该先遍历背包再遍历物品
代码示例-java
class Solution {
public int combinationSum4(int[] nums, int target) {
// 定义dp数组
int[] dp = new int[target+1];
// 初始化
dp[0] = 1;
// 遍历
for(int j=0;j<=target;j++){
for(int i=0;i<nums.length;i++){
if(j>=nums[i]){
dp[j] += dp[j-nums[i]];
}
}
}
return dp[target];
}
}
总结
1.求组合或排列的递推公式为 dp[j] += dp[j-weight[i]];
2.完全背包遍历时,背包应该从小到大遍历
3.如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。