给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 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
注意:
你可以假设:
0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过 500 种
结果符合 32 位符号整数
思路:
• [0-1]背包 问题:当前考虑的物品拿或者不拿;
• [完全]背包 问题:当前考虑的物品拿或者不拿,如果拿,只要背包能装下,就可以一直拿,直到背包装不下为止。
• 此题:动态规划(完全背包问题),求组合数,设 dp[i][j] 表示使用前 i种硬币组成金额 j 的组合数;
• dp[i][j] = dp[i-1][j] + dp[i][ j-coins[i] ] ,即,对于当前的硬币 coins[i] 来说,组成金额 j 有两种情况,如果不使用这个硬币 i,而是使用前 i-1个硬币组成金额 j 的组合数为 : dp[i-1][j] ,如果使用这个硬币 i,则组合数等于 : dp[i][j - coins[i] ];
注:图片来源 Liucx
• 一个一个物品考虑,容量一点一点扩大,整个过程就是尝试和比较的过程。
• 代码(二维矩阵):
class Solution { public int change(int amount, int[] coins) { int n = coins.length; int[][] dp = new int[n + 1][amount + 1]; dp[0][0] = 1; //当 amount = 0 时,有 1 种方案 for(int i = 1; i <= n; i++){ // 第 i 个硬币,从下标 1 开始 int coin = coins[i-1]; //硬币金额 for(int j = 0; j <= amount; j++){ //背包容量从 0 慢慢扩大到 amount if(j < coin) dp[i][j] = dp[i-1][j]; //当背包容量小于当前硬币金额,此硬币不用考虑 else dp[i][j] = dp[i-1][j] + dp[i][j-coin]; //否则,一直放当前硬币 } } return dp[n][amount]; // n 个硬币都放完,背包容量为 amount 的组合数 } }
• 优化,利用滚动数组进行空间压缩;
• 代码(一维数组):
class Solution { public int change(int amount, int[] coins) { int[] dp = new int[amount + 1]; dp[0] = 1; //当 amount = 0 时,有 1 种方案 for(int i = 0; i < coins.length; i++){ int coin = coins[i]; //硬币金额 for(int j = coin; j <= amount; j++){ //背包容量从当前硬币 coin 慢慢扩大到 amount dp[j] = dp[j] + dp[j-coin]; } } return dp[amount]; } }