给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 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 位符号整数
根据题目可以使用暴力搜索解决,但是由于amount和coin的种类的限定2^5000一定是超时的,提交的一发果不其然。code如下:
public static int change(int amount, int[] coins) {
int[] path = new int[amount];
Arrays.sort(coins);
return dfs(coins,amount,0,path);
}
private static int dfs(int[] coins, int amount,int cur,int[] path) {
if(amount == 0) {
return 1;
}else if(amount < 0) {
return 0;
}
int results = 0;
for(int t = 0; t < coins.length; t++) {
if( amount >= coins[t] ) {
if(0==cur || path[cur-1] <= coins[t]) {
path[cur] = coins[t];
int r = dfs(coins, amount - coins[t], cur + 1, path);
results += r;
}
} else {
break;
}
}
return results;
}
定义arrs[S]代表表示面值为S时的组合数. 在搜索的时候如何arrs[]数组中的值不是-1的话直接只用当前的数
现在考虑由d[S-coins[i]]能否算出arrs[S]。
例如:考虑面值S=1,2,3,4,5,6时,coins=[1,2,5]的组合情形
arrs[1]=1,arrs[2]=2
现在,面值S=3,考虑arrs[3],
选中coins[0] = 1 , S - coins[0] = 2 。而arrs[2] = 2 , d[3] = 2
选中coins[1] = 2 ,S - coins[1] = 1 。而arrs[1] = 1,d[3] =2+ 1 = 3
显然arrs[3] != 3 是2
这说明d[S]的定义无法排除重复组合。需要扩充定义。
设d[S][j]表示面值为S,组合的第一个硬币是coins[j]时的组合种数。还是要求组合中,硬币的面值递增。
d[1][0]=1
d[2][0]=1 , d[2][1]=1
那么d[3][0],剩余面值3-1=2。面值2时,选择第一个硬币是coins[0]=1。d[2][0]=1。 选择第一个硬币是coins[1]=2。d[2][1]=1。 d[3][0]=d[2][0]+d[2][1]=2。
d[3][1], 剩余面值3-2=1。面值1小于面值2,这种组合被排除。 d[3][1] =0。
面值S=3的总组合数=d[3][0]+d[3][1]=2+0=2.
code:
public int change(int amount, int[] coins) {
int[] path = new int[amount+1];
//arrs[s][i] 表示截至第i个数组成s一共有多少种
int[][] arrs = new int[amount+1][coins.length+1];
for(int t = 0;t <= amount;t++)
Arrays.fill(arrs[t],-1);
Arrays.sort(coins);
int rs = dfsOPT(coins,amount,0,path,arrs);
return rs;
}
private int dfsOPT(int[] coins, int amount,int cur,int[] path,int[][] arrs) {
if (amount == 0) {
return 1;
} else if(amount < 0) {
return 0;
}
int results = 0;
for(int t = 0; t < coins.length; t++) {
if( amount >= coins[t] ) {
if(0==cur || path[cur-1] <= coins[t]) {
if(arrs[amount][t] != -1) {
results += arrs[amount][t];
} else {
arrs[amount][t] = 0;
path[cur] = coins[t];
int r = dfsOPT(coins, amount - coins[t], cur + 1, path,arrs);
arrs[amount][t] = r;
results += r;
}
}
} else {
break;
}
}
return results;
}
经典完全背包完全背包求解 答案总数,直接上代码:
public static int change(int amount, int[] coins) {
int len = coins.length;
//dp[i]表示截至数组中第i个数有多少种方式成amount的钱数
int[] dp = new int[amount+1];
dp[0] = 1;
for(int i = 0;i < len;i++){
for(int j = coins[i];j <= amount;j++){
dp[j] += dp[j-coins[i]];
}
}
return dp[amount];
}
记忆化搜索可能需要保存一个巨大的记录矩阵,但是动态规划有时可以采用滚动数组来减掉一维或更多,空间有优势。(你的搜索用独特的记录方式除外)