题目链接:518. 零钱兑换 II - 力扣(LeetCode)
前情提要:
因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。
最近刚学完背包,所以现在的题解都是以背包问题为基础再来写的。
如果大家不懂背包问题的话,建议可以去学一学01背包和完全背包。
如果大家感兴趣,我后期可以出一篇专门讲解背包问题。
dp五部曲。
1.确定dp数组和i下标的含义。
2.确定递推公式。
3.dp初始化。
4.确定dp的遍历顺序。
5.如果没有ac打印dp数组 利于debug。
每一个dp题目如果都用这五步分析清楚,那么这道题就能解出来了。
这里下文统一使用一维dp数组。
题目思路:
其实该题还是比较好入手,它的题目描述很清楚了,给定一个整数amount要我们求用coins里的硬币来填满它的方法数量。
如果你做过494. 目标和 - 力扣(LeetCode)的这道题,或者你看过我目标和这道题解力扣494-目标和(Java详细题解)-CSDN博客
你会发现这俩求的一样,都是求将背包装满的方法数量。
此时amount就是一个容器,coins数组里的元素就是物品,而且该物品可以重复添加,所以该问题可以抽象为一个完全背包问题。
完全背包与01背包不同的地方有俩个,一个是选择物品的数量不同。
01背包每个物品只能选一次,完全背包每个物品可以选无数次。
还有一个就是代码上的不同,代码实现选择物品的数量是不一样的。
01背包遍历dp数组的遍历顺序是先遍历物品后遍历背包,而且背包是从后往前遍历,这个才能确保每个物品只能选择一次。
而纯完全背包问题,它的dp遍历顺序先遍历物品和先遍历被背包都可以,背包是从前往后遍历,这样可以让每个物品选择多次。
01背包和完全背包的不同就讲完了,我们来具体分析分析该题。
1.确定dp数组和i下标的含义。
dp[j] 就是指装满j容量的背包的方法数量。
2.确定递推公式。
该题的递推公式和力扣494-目标和的一样,都是dp[j] += dp[j - nums[i]];
具体怎么来的如下。
每个物品只有选和不选俩种状态。不选的状态就是dp[j],因为没有选,背包容量不会减少。此时就是不选当前物品时能装满的方法的数量。
选的状态就是dp[j - nums[i]],因为此时我们选择了该物品,我们就要求在装入该物品之前的装满背包的方法种类的数量然后再放入该物品。
放入该物品的状态就是dp[j - nums[i]]。
肯定有人疑问为啥不加1呢? 此时我们是选择装入该物品,首先我们得知道装入该物品之前装满的方法数量,那么加上该物品其实并不会让该方法数量加一,已经选择了该物品,所以方法数量就是dp[j - nums[i]]。
举个例子。比如我们选择了当前物品1,此时背包容量为4。
此时选择的状态就是dp[j - 1] = dp[3]。
就是装入该物品之前装满的方法数量,而此时我们其实已经选择了装入了当前物品 ,也就是说此时装满背包容量为4选择当前物品的状态就是dp[3]。
因为这个一种方法,而不是物品的数量,如果是数量,那么加入该物品肯定是要加1,但是这是一种选择方法,我已经选择了,所以就不会加1。
所以我们的递推公式就能推出,因为每个物品只有选和不选俩个状态,所以dp[j] = dp[j] + dp[j - nums[i]];
也就是当前背包容量为j的装满的方法数量,等于他不选择当前物品和选择当前物品的总方法数量。
所以再精简点就是dp[j] += dp[j - nums[i]];
3.dp初始化。
这里的初始化也和力扣494-目标和的一样,dp[0] = 1;
具体怎么来的如下。
那么dp[0]初始化为多少呢,dp[0]其实就是当背包容量为0时,所能装满的方法数量。
背包容量为0,我们是不是能想到此时背包就是满的,因为他不能放东西嘛,所以此时对他来说也就是满的。
所以dp[0] = 1。
4.确定dp的遍历顺序。
遍历顺序就不一样了。完全背包的遍历顺序和01背包的遍历顺序就大不一样,上面有讲解。
纯完全背包问题先遍历物品或者先遍历背包都可以,但本题不一样,该题要求的其实是一种装满amount的物品组合。
组合是没有顺序的,就是选择物品组合为{1,2}和{2,1}其实是一种组合,而对于排列来说这是俩种,排列是有顺序的。
这里我就直接给出方法论,大家现阶段就会用即可,后面深入的时候可以想想具体的实现。
先遍历物品后遍历背包就是组合。先遍历背包后遍历物品就是排列。
所以该题就是要先遍历物品后遍历背包。
5.如果没有ac打印dp数组 利于debug。
每一个dp题目如果都用这五步分析清楚,那么这道题就能解出来了。
举例推导dp数组。
最终代码:
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] + dp[j - weight[i]];
//每个元素只有选和不选 所以当背包容量为3时应该是要加上没选物品3时装满的方法和选上物品3时装满的方法
//当背包容量为3 物品3我可以选也可以不选 不选的dp[j] 选的就是dp[j - weight[i]]
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
}
背包问题就是这样,思路分析一大堆,实际代码一小堆。
我们多做多理解就好。
这一篇博客就到这了,如果你有什么疑问和想法可以打在评论区,或者私信我。
我很乐意为你解答。那么我们下篇再见!