题目描述
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
样例
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
注意:
你可以假设:
- 0 <= amount (总金额) <= 5000
- 1 <= coin (硬币面额) <= 5000
- 硬币种类不超过 500 种
- 结果符合 32 位符号整数
题解
暴力递归
- 用0张coins[i]的货币,让i之后的货币组成amount,最终方法数记为res1;
- 用1张coins[i]的货币,让i之后的货币组成amount-coins[i],最终方法数记为res2;
- 用k张coins[i]的货币,让i之后的货币组成amount-coins[i]*k,最终方法数记为resk;
- 所有的方法数相加即为最终的结果
public int change(int amount, int[] coins) {
if(coins==null||coins.length==0||amount<0)
return 0;
return process(amount,coins,0);
}
private int process(int amount, int[] coins,int index) {
int res=0;
if(index==coins.length)
res=amount==0?1:0;
else {
for(int i=0;coins[index]*i<=amount;i++)
res+=process(amount-coins[index]*i,coins,index+1);
}
return res;
}
对暴力递归的优化
比如coins[]={5,10,25,1},amount=1000
- 0张5元1张10元的结果和2张5元0张10元的结果是类似的,都需要去用剩下的硬币产生金额为990的情况,即process(990,coins,2)会被重复执行,我们应该舍去这种重复的计算
- 通过把每次process计算的结果存下来,避免产生重复的计算
- 定义一个二维数组map,map[i][j]=0表示process(i,j)重来没有被计算过,map[i][j]=-1表示计算过但返回值为0,否则map[i][j]表示process(i,j)的计算结果
动态规划
dp[i][j]表示在可以使用前i种货币的前提下,组成金额j的组合数
- 当i=0时只有j可以整除i的情况下dp[0][j]=j/i;否则为-1表示只使用第一种货币无法产生金额j
- 当j等于0时,dp[i][j]=1,即产生金额0的方法只有1种
- 除去第一行和第一列,其他位置的取值依赖于以下几种情况
- 完全不用coins[i]货币,只使用前i-1个货币时方法数为dp[i-1][j]
- 仅用k张coins[i]货币,方法数为dp[i-1][j-k*coins[i]]
- 将上述结果累加即为dp[i][j]
对动态规划的优化;
dp[i][j]=dp[i-1][j]+dp[i-1][j-coins[i]
对空间复杂度的优化:dp[j]+=j-coins[i]>=0?dp[j-coins[i]]:0
public int change(int amount, int[] coins) {
if(amount==0)
return 1;
if(coins==null||coins.length==0||amount<0)
return 0;
int[]dp=new int[amount+1];
dp[0]=1;
for(int i=0;i<coins.length;i++) {
for(int j=1;j<=amount;j++) {
if(i==0) {
if(j%coins[0]==0)
dp[j]=1;
}else {
if(j-coins[i]>=0)
dp[j]+=dp[j-coins[i]];
}
}
}
return dp[amount];
}