518. 零钱兑换 II
题目链接:
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。
示例 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
提示:
-
1 <= coins.length <= 300
-
1 <= coins[i] <= 5000
-
coins
中的所有值 互不相同 -
0 <= amount <= 5000
解题思路
题目中说“假设每一种面额的硬币有无限个。 ”,通过这一句话可以判断这是一道完全背包问题。
但本题要求的是凑成总金额数有几种方法,而不是求能得到的最大金额数。
而且本题不强调元素之间的顺序,属于组合问题,排列问题是强调元素之间的顺序的。
运用动态规划五部曲解决问题:
-
确定dp数组以及其下标的含义
dp[j]
表示凑成金额数为j的货币组合有多少种 -
确定递推公式
不考虑
coins[i]
的情况下,凑成金额数为j - coins[i]
的货币组合方法有dp[j - coins[i]]
种。也就是当前凑成金额数为
j
的方法数 = 之前凑成金额数为j
的方法数 + 之前凑成金额数为j - nums[i]
的方法数。于是得到递推公式为:dp[j] = dp[j] + dp[j - coins[i]]
-
dp数组的初始化
用0个货币凑成金额数为0的方法有一种。
所以
dp[0] = 1
下标非0的
dp[j]
初始化为0,这样累计加dp[j - coins[i]]
的时候才不会影响真正的dp[j]
-
确定遍历顺序
在
总结:
-
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
-
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
-
-
举例推导dp数组(略)
C++
class Solution { public: int change(int amount, vector<int>& coins) { vector<int> dp(amount + 1, 0); dp[0] = 1; for (int i = 0; i < coins.size(); i++) { for (int j = coins[i]; j < amount + 1; j++) { dp[j] = dp[j] + dp[j - coins[i]]; } } return dp[amount]; } };
JavaScript
/** * @param {number} amount * @param {number[]} coins * @return {number} */ var change = function(amount, coins) { const dp = Array(amount + 1).fill(0); dp[0] = 1; for (let i = 0; i < coins.length; i++) { for (let j = coins[i]; j < amount + 1; j++) { dp[j] = dp[j] + dp[j - coins[i]]; } } return dp[amount]; };