给你一个整数数组 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
理解: 这是一种组合优化问题,我们所选的硬币之间没有什么特定的联系,我们只需要选择他们,并把他们凑成我们所需要的样子。
本题我们很容易就可以看出是完全背包。
背包问题的概念和解决方案
朴素解法: arr[i][j]定义为前 i 种硬币凑成总金额为 j 的方案总和。
显然对于第 i 种硬币我们拥有着两种选择方案:
1、直接不选:arr[i][j] = arr[i - 1][j]
2、由于可以选任意个(不超出范围的情况下),所以 :
arr[i][j] += arr[i - 1][j - k *value],0 <= k <= [j / value],value为当前硬币的价值。
附:arr[0][0] = 1, arr[0][x] = 0;
class Solution {
public int change(int amount, int[] coins) {
int length = coins.length;
int[][] arr = new int[length + 1][amount + 1];
arr[0][0] = 1;
//循环,表示前 i 种硬币
for(int i = 1; i <= length; i++){
//表示前 i 种硬币,总金额为 j 的组合方案。
for(int j = 0; j <= amount; j++){
//状态转移。
for(int k = 0; k * coins[i - 1] <= j; k++){
arr[i][j] += arr[i - 1][j - k * coins[i - 1]];
}
}
}
return arr[length][amount];
}
}
空间优化: 上面那个解法的空间复杂度和时间复杂度都挺离谱的,让我们先去优化一下它的空间。
因为我们选取前 i 种硬币凑成金额为 j 的方案总数,其实只和前 i - 1种硬币的方案有关。
class Solution {
public int change(int amount, int[] coins) {
int[] arr = new int[amount + 1];
arr[0] = 1;
for(int value : coins){
//表示前 i 种硬币,总金额为 j 的组合方案。
//因为我们的arr[i][j]其实,只要考虑arr[i - 1][j - k*coins[i - 1]]
//所以我们只需要从大到小改变,那么就不会对相对金额小的结果产生影响。
for(int j = amount; j >= value; j--){
//状态转移。
for(int k = 1; k * value <= j; k++){
arr[j] += arr[j - k * value];
}
}
}
return arr[amount];
}
}
进行时间上的优化: 从后往前改变是我们优化空间的常用办法,但是在优化种你会发现,arr[i][j] = arr[i - 1][j] + arr[i][j - value] (可以自己推到下)
所以:
class Solution {
public int change(int amount, int[] coins) {
int[] arr = new int[amount + 1];
arr[0] = 1;
for(int value : coins){
for(int j = value; j <= amount; j++){
arr[j] += arr[j - value];
}
}
return arr[amount];
}
}