LeetCode 零钱兑换 II
@author:Jingdai
@date:2021.09.12
题目描述(518题)
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
示例输入
amount = 5, coins = [1, 2, 5]
示例输出
4
解释
解释:有四种方式可以凑成总金额: 5=5 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1
思路及代码
这个题目是典型完全背包问题,看了很多题解,都没有说明白这个题目是如何转换为完全背包的,所以本文将会从完全背包一步步推导到最简的式子。后面的讲解需要有一点点01背包和完全背包的知识,这里不再讲这些基础。
把题目中的 amount 看成容量,coin 看成物品,物品的重量和价值都是 coin 值。那么这个题目就是求完全背包的方案数,同时,题目要求完全背包必须装满的方案数。和完全背包一样,用一个 dp 记录最大的价值,由于还需要记录方案数,所以用第二个数组 dp2 记录方案数。
初始化,对于没有装满的背包,这是一个无效状态,dp 中的值我们初始化为负无穷大,而 dp2 中的值为0。对于dp[0] ,背包容量为0,0件物品,相当于装满了,是有效状态即 dp[0] 初始化为0。而对于 dp2,dp2[0] 容量为0,0件物品,装满了,即有一个方案,所以初始化为1。
递推,dp 数组的更新和完全背包一样,主要看 dp2 数组的更新。wi 代表第 i 件物品重量,vi 代表第 i 物品价值,在本题中都一样,都是 coin 的值。
当 dp[j-wi] + vi > dp[j] 时,dp[j] = dp[j-wi] + vi。dp2呢?因为它从j-wi更新过来,所以也应该更新为这个,即 dp2[j] = dp2[j-wi]。
当 dp[j-wi] + vi = dp[j] 时,dp[j] 就不用更新了,但是 dp2 还是要更新,因为它多了从 j-wi 转移过来的方案,所以dp2[j] += dp2[j-wi]。
当 dp[j-wi] + vi < dp[j] 时,dp[j] 不用更新,同时方案数也没有变,所以 dp2[j] 也不用更新。
根据此,就可以完成如下代码。
public int change(int amount, int[] coins) {
if (amount == 0) {
return 1;
}
int n = coins.length;
int