一、先来看看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;
}

 

 

 

收藏 打印