完全背包问题的学习与思考
01背包设置一维dp数组遍历顺序的讨论
在看这一篇文章之前,建议先看我的另一篇文章 01背包的学习与思考
在01背包的学习与思考 这篇文章中,针对状态压缩,即在讨论如何将dp数组设置为一维数组? 的时候,实际上有提到,遍历顺序为先遍历物品再遍历背包的时候,针对01背包问题,我们在遍历背包时(也就是遍历一维dp数组时),要求下标从大到小进行遍历。
在01背包文章中也已经提到,下标从大到小进行遍历,根据其递推公式dp[j]=Math.max(dp[j],dp[j-curWeight]+curValue); 这里面dp[j-curWeight]指向的一定是下图中的黄色部分,即“历史”,而如果下标从小到大进行遍历,下标j-curWeight指向的实际上是前置的蓝色,如下图的第二部分,可能在j=0的时候,已经放入了物品0,而在j=1的时候,又会继续放入物品0,这就违背了01背包的每个物品只能被放入一次。
完全背包问题的概念
但是,我们这里讨论的是完全背包问题。
完全背包问题的概念:有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次) ,求解将哪些物品装入背包里物品价值总和最大。
这意味着什么?这意味着,正好套上了前面01背包问题,创建一维dp数组,但是遍历顺序是从小到大进行遍历的特点。背包大小从小到大进行遍历,对于01背包问题而言是违反了 01的概念,但是却恰好契合了完全背包的概念。
这里我们仍然还是采取01背包问题中的例子,如下表所示:(背包的最大容量是4)
| 重量 | 价值 | |
|---|---|---|
| 物品0 | 1 | 15 |
| 物品1 | 3 | 27 |
| 物品2 | 4 | 34 |
那么我们手动来比划,针对完全背包,物品0~物品2都可以无限重复取,最大价值如下:
可以很容易地推导到,最大的价值实际上是不断地、反复地去放物品0。
完全背包问题的组合和排列问题
在完全背包的应用问题中,实际上还区分为排列问题和组合问题。
这里我们直接说结论:
- 完全背包求组合数,那么就要先遍历物品,再遍历背包
- 完全背包求排列数,那么就要先遍历背包,再遍历物品。
关于组合和排列的说明:
比如说我们现在有面额为1和2的硬币,数量有无数个,那么凑成面额为5元的方案有多少种?
其中一定存在这两个情况: 1、2、2 和 2、1、2 。
对于组合来说,这俩就是一个方案,因为组合 不考虑顺序问题。
对于排列来说,这俩就是两个方案,因为排列,要考虑顺序问题。
这里抛出一个疑问,为什么呢? 为什么偏偏先遍历物品再遍历背包 就是 求解组合数;先遍历背包再遍历物品,就是求解排列数呢?
这里实际上仍然举个例子就行了,我们简化一下上面的示例,比如我们现在手里有面额为1和2的硬币,凑成面额为3元
- 显而易见,无法两种凑合方式 1、2 和 2、1
- 凑成面额为3的组合方式,结果为1,因为1、2和2、1是一样的,顺序无关
- 凑成面额为3的排列方式,结果为2,因为1、2和2、1的顺序不一样。
先遍历物品再遍历背包,只能是组合,而不能是排列。
- 物品只有1和2,背包就是一维dp数组,背包容量从0~3,那么想象一下,先遍历物品再遍历背包,当遍历到物品为1时,会用这个1把整个dp数组都刷一遍,然后遍历到物品2时,会再用这个2把整个dp数组再刷一遍,最后得到结果。
- 那么!这样的话,就只有1、2 何来的2、1 ?!!! 所以只能解决组合问题。
先遍历背包再遍历物品,解决的是排列问题
- 先遍历背包,比方说现在遍历背包容量为1时,此时针对这个容量,再遍历物品,会用1,2都刷一遍;然后到了背包容量为2,此时针对容量2,又用物品1,2再刷一遍。这时候物品1、2和物品2、1顺序都兼顾到了
题目实例:
组合问题:518.零钱兑换II
这道题里面显式地提出了返回可以凑成总金额的硬币组合数
-
确定dp数组及其含义:
int[]dp=new int[amount+1];dp[j]的含义为在总金额为j的情况下,能够通过硬币凑成金额为j的硬币组合数
-
确定递推公式:
- 当总金额
j>= coins[i]的情况下,即当前硬币的面额能够加入到总金额j的组成之后,构成总金额j,能够有几种方法。 - 当总金额
j<coins[i]的情况下,即当前硬币的面额无法加入到总金额j的组成之中,保持现状dp[j]=dp[j]即可
- 当总金额
-
dp数组初始化:
- 这里特别说明一下为什么要设置
dp[0]=1。 - 实际上也非常简单,比方说当前要凑成的总金额为2,然后手里面的硬币面额为2,肯定是能凑成的,确确实实是一种方法,那么
dp[2-2]即dp[0]一定要是1
- 这里特别说明一下为什么要设置
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=1;j<=amount;j++){
if(j>=coins[i]){
dp[j]=dp[j]+dp[j-coins[i]];
}
}
}
return dp[amount];
}
}
排列问题377. 组合总和 Ⅳ
这道题实际上名字取的有点问题,说是组合总和,但是实际上看其示例,本质上求得是排列问题
class Solution {
public int combinationSum4(int[] nums, int target) {
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]){
continue;
}
else{
dp[j]+=dp[j-nums[i]];
}
}
}
return dp[target];
}
}