完全背包与01背包的区别在于每种物品都有无限件,可以多次放入背包。
我们回顾一下01背包的遍历顺序,其中内层遍历背包的过程要后序遍历,为什么当时一定要后序呢?
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
要求的当前节点数据用到的是前面的节点,我们后序遍历就能保证用到的前面节点是还没考虑放入当前物品i的情况。由此,我们可以发现背包正序遍历可以实现多重背包问题,因为我们遍历容量是逐一递增的,所以每次递增最多也就多放一个物品i,不会出现说背包循环走一步可以放多个物品i,毕竟我们默认物品i重量是≥1的,<1的不考虑,应该也不会这么出题。
一些注意点:
一维dp数组实现的完全背包问题中,遍历背包和物品顺序可不可以调换?
现在我们回过头去思考此前01背包中,先遍历背包后遍历物品会怎么样?
这里我们肯定还是得保持背包后序遍历,这样的话一开始前面的背包都是清空状态,无法给最大的背包提供有效信息,它最终不一定能放最大价值,往后讨论也就没有意义了。
**注意注意:**先遍历物品得到的是组合;先遍历背包得到的是排列。在下面两题求解背包装满的方法数量有所体现。
代码:(01背包在遍历顺序上做了小调整)
//先遍历物品,再遍历背包
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 + " ");
}
}
题目介绍
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
示例 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
示例 2:
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。
示例 3:
输入:amount = 10, coins = [10]
输出:1
个人思路:
本题很明显是背包问题,最大容量为amount,要我们求装满背包的最多方案,此外我们发现物品可以重复放入,故而这是一个完全背包问题。
动规五部曲
确定dp数组及其下标含义
int[] dp = new int[amount + 1];
//dp[j] 表示 容量为j的背包装满的方法 此题吧面额和重量看成一样
确定递推公式
dp[j] += dp[j - coins[i]];
还是放与不放物品i的问题引入,装满容量为j的背包的方法 =已知方法数量 + 装满容量j拿出一个物品i的容量的背包的方法数量
初始化dp数组
dp[0] = 1;
计算装满方法,统一操作了。
遍历顺序确定
虽然我们前面说完全背包求最大价值两次for循环可以调换顺序,但在这里不可以调换顺序,这里求的是组合方法,放满的方法数量。
先遍历物品,这种情况是组合情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3RYchxMU-1676306021084)(C:\Users\耿飞扬\AppData\Roaming\Typora\typora-user-images\image-20230214001843020.png)]
先遍历背包,这种情况会出现排列的情况,以遍历到背包3举例,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xtPSDG7D-1676306021085)(C:\Users\耿飞扬\AppData\Roaming\Typora\typora-user-images\image-20230214002731595.png)]
背包要正序遍历
打印dp数组检验
代码
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
//dp[j] 表示 容量为j的背包装满的方法 此题吧面额和重量看成一样
dp[0] = 1;
for (int i = 0; i < coins.length; i++) {
for (int j = 1; j <= amount; j++) {
if (j - coins[i] >= 0)
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
}
题目介绍
给你一个由 不同 整数组成的数组 nums
,和一个目标整数 target
。请你从 nums
中找出并返回总和为 target
的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
示例 2:
输入:nums = [9], target = 3
输出:0
个人思路
原本这题是没写出来的,上一题可以说是碰巧过的吧,没有仔细思考排列组合受到循环内外层调换的影响。
上一题理清之后,本题就直接过了。思路也不过多阐述了,直接上代码,具体见上一题思路解析
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
//dp[j]:背包容量为j的背包装满的方法为dp[j]
//初始化
dp[0] = 1;
for (int j = 1; j <= target; j++) {
for (int i = 0; i < nums.length; i++) {
if (j - nums[i] >= 0)
dp[j] += dp[j - nums[i]];
}
}
return dp[target];
}
}