一、先来看看Coin change
输入是不同的面额和一个总金额,写一个函数计算组成这个总金额所需的最少的硬币数目。如果无法组成该金额,返回-1。
Example 1:
Input: coins = [1, 2, 5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1
Example 2:
Input: coins = [2], amount = 3
Output: -1
思路:假设dp[i]是组成i的最少硬币数,考虑去掉一个面额x的硬币的情况,dp[i-x]是否是组成i-x的最少硬币数?
用反证法,如果dp[i-x]不是组成i-x的最少硬币数,那么就可以用少于dp[i-x]的硬币数组成i-x,相应的i就可以用这个更少的组合+一块x面额的硬币组成,那么大前提里的dp[i]就不是最少硬币数,矛盾。
结论,如果dp[i]是组成i的最少硬币数,那么去掉一个面额为x的硬币后,dp[i-x]也是组成i-x的最少硬币数。
动态规划的转移方程就出来了:dp[i] = min{ dp[i], dp[i-coins[j]] + 1 }
代码:
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
Arrays.fill(dp,amount+1); //组成amount的最小硬币数 最少是amount个,也就是全1的情况
dp[0] = 0;
for(int i=1; i<=amount; i++)
for(int j=0; j<coins.length; j++)
if(i>=coins[j])
dp[i] = Math.min(dp[i],dp[i-coins[j]]+1);
return dp[amount]==amount+1?-1:dp[amount];
}
二,求一种具体方案
前面只是算出了最少需要多少硬币,但是没有给出具体方案。下面考虑一下如何给出具体方案,
其实可以根据dp[amount]是如何推出来了,去反推出方案。打个比方,现在有coins = [2,3,5],dp[12]求出来是3
那么dp[12]一定是根据dp[10],dp[9],dp[7]中的最小值dp[12-x]退出来的,且有dp[12] = dp[12-x]+1,也就意味着这一步用了一个面值是x的硬币。这样一直朝前推就可以把方案算出来。
private static int[] fangan(int amount, int[] coins, int[] dp) {
int[] count = new int[coins.length];
int t = amount;
while (t > 0)
for (int i = 0; i < coins.length; i++)
if (t >= coins[i])
if (dp[t] == dp[t - coins[i]] + 1) {
count[i] += 1;
t -= coins[i];
break;
}
return count;
}
这里只能求出一种方案,并不能求出所有方案,比如如果amount=9的话,那么count = [2, 0, 1]。但实际上,我们知道,3个3的方案也是可行的。
这里可以试着把break;去掉,也是可以找到一个方案的,虽然没有加上break;好理解,但是其实逻辑是一样的。
三,求所有方案
还是上面的例子,amount=9可能有多个多种方案,那么amount-conis[i]最多有三种方案,然后再去递归amount=7,6,4的方案即可。具体怎么写,待我神功大成之日再来写。