518.零钱兑换Ⅱ

518.零钱兑换Ⅱ

题目

给你一个整数数组 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change-2
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

从一个数组中按一定的选取方式得到目标target,那么尝试将题目进行转化为背包问题。
这里的物品可以重复选择,那么背包应该是完全背包。

背包的容量是amount,物品是coins[i],物品的重量是coins[i]

1.确定dp数组以及下标的含义
dp[j] 表示填满背包容量j有dp[j]种方法。

2.确定递推公式
dp[j]如何推出来?

  • 填满j-coins[i]的背包有dp[j-coins[i]]种方法,也就是如果当前coins[i]放入背包那么就有dp[j-coins[i]]种方法
  • 之前填满容量为j背包的方法数,也就是当前coins[i]不放进背包

说的更加直白一点,那就是用前k的硬币凑齐金额i,要分为两种情况计算,
一种是没有用第K个硬币就凑齐了,一种是前面已经凑到了i-coins[k],现在就差第k个硬币了。

两种方法都可以选择,那么dp[j] = dp[j] + dp[j-coins[i]]

3.dp数组如何初始化
dp[0] = 1,有一种装法

4.确定遍历顺序
先遍历物品,再遍历背包

   for(int coin : coins){
            for(int j = coin;j<=amount;j++){
                 dp[j] += dp[j-coin];
            }
 }

这里的遍历顺序是不可以变的。

先枚举金额,对于金额来说每次硬币循环都是可以的,1+2和2+1代表了两种方案,也就是排列问题。

但是先枚举硬币,那么只是对相应的金额进行方案添加,之前的硬币使用后不能再次使用。1+2可行,但是2+1就不会再出现,是组合数。

把 dp table 退化成二维的,在看看推导方向就很容易明白了,如果二维的 dp[i][j] 按一般思路,i放 nums ,那么实际推导过程是竖着遍历的,所以做状态压缩的时候,遍历方向需要保持正常遍历的方向,所以需要先遍历 target,在遍历 nums。但是如果i是target ,那么推导过程是正常的遍历顺序。

5.举例推导dp数组
输入:amount = 5, coins = [1, 2, 5]
image

代码

class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount+1];
        dp[0] = 1;
        for(int coin : coins){
            for(int j = coin;j<=amount;j++){
                 dp[j] += dp[j-coin];
            }
        }
        return dp[amount];
    }
}
posted @ 2021-09-26 22:18  rananie  阅读(41)  评论(0编辑  收藏  举报