题目描述
给你一个整数数组 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
提示:
1 <= coins.length <= 300 1 <= coins[i] <= 5000 coins 中的所有值 互不相同 0 <= amount <= 5000
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/co… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
完全背包🎒-组合数问题
完全背包🎒问题典型特点是:物品数量不限; 完全背包四部曲:
- 定义dp数组:
- dp[j]代表兑换零钱总金额amount的硬币组合数;
- 递推公式:
- dp[j] 上次递推结果加上dp[j-coins[i]]所有组合数;
- 所以公式为:dp[j]+=dp[j-coins[i]]
- dp 数组初始化
- 从题意入手:dp[0],表示凑成零钱总金额0元的组合数为1,
- dp[0]=1,为递推公式的基础;
- 下标非0的dp[j],应初始化为0,这样在计算dp[j]+dp[j-coins[i]]不会影响到dp的初始计算;
- 遍历物品还是背包容量的for循环顺序,是否影响结果
- 本题是求零钱兑换的组合数,与排列顺序无关; 我们来看先遍历物品再遍历容量的代码:
这时候计算dp[j]的顺序一定是按照coins[]数组顺序遍历; 如:coins[0] = 1,coins[1] = 2。组合为{1,2};而不会出现{2,1} 我们再来看看先遍历容量,在遍历物品的代码:for (int i = 0; i < coins.length); i++) { // 遍历物品 for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量 dp[j] += dp[j - coins[i]]; } }
for(let j=0;j<=amount;j++){ // 先遍历容量 for(let i=0;i<coins.length;i++){ // 后遍历物品 if(j-coins[i]>=0){ dp[j]+=dp[j-coins[i]] } } }
这时候计算出来的每一个容量dp[j],都包含{1,2}和{2,1}这样的排列之和的计算;
总结:
在求装满背包🎒问题顺序关键在于:
- 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
- 如果求排列数就是外层for遍历背包,内层for循环遍历物品。
代码
/**
* @param {number} amount
* @param {number[]} coins
* @return {number}
*/
var change = function(amount, coins) {
// 完全背包问题;求硬币组合数、,初始化为0; 每一种面额硬币无限
let n=coins.length;
//1、初始化 初始化dp容量,amount===0时,组合数为0;
let dp=new Array(amount+1).fill(0)
dp[0]=1;
for(let i=0;i<n;i++){
for(let j=coins[i];j<=amount;j++){
dp[j]+=dp[j-coins[i]]
}
}// end of for
return dp[amount]
};