描述:给定总金额为A 的一张纸币,现要兑换成面额分别为a1, a2,…, an 的硬币。硬币兑换问题是用最少枚数的硬币来兑换总金额为A 的纸币。
如 a = {1, 2, 5},兑换6元则要2枚硬币(1和5)。根据特定的a,有可能可以直接用贪心算法每次都选取可用的最大硬币值。但对于一些例子,会出现错误情况。
如 a = {1, 4, 5},兑换8元。若直接选取最大值,则需要4枚(1个5和3个1),但显然最优值应为2枚(2个4)。
对于兑换n元的硬币的最优情况,可以遍历列表 a ,分别选取1个其面值的硬币,再从中挑出最优结果,此时 f(n) = min{f(n-ai)+1}。而每个之前算法的结果都可能被后面的结果重复使用。满足动态规划中的最优子结构和重叠子问题。
算法基本思路:
1.计算最小枚数;
如前所述,设 f[i] 表示兑换 i 元所需要的最小枚数硬币,初始化 f[0] = 0.
f[i] = min{f[i-a[j]] + 1}, 其中 i >= a[j], 0 <= j < a.length
2.构造最小枚数所需的各个硬币。
在上面的基础上,设 rest[i] 表示经过本次兑换后所剩下的面值,即 i - rest[i] 可得到本次兑换的硬币值。
从 k = n 开始,逆推可构建整个路线(k = rest[k])。
public class ChangeCoins{ public static int getChanges(int[] a, int n){ /* a: all kinds of coins minNum: the min number of coins rest: the rest money after using this coin */ if(n < 0) return -1; //init int[] minNum = new int[n+1]; int[] rest = new int[n+1]; minNum[0] = 0; //dynamic programming for(int i = 1; i <= n; i ++){ int min = i; for(int j = a.length-1; j >= 0; j --){ if(i >= a[j] && min > 1 + minNum[i-a[j]]){ min = 1 + minNum[i-a[j]]; rest[i] = i-a[j]; } } minNum[i] = min; } //build the path int k = n; while(rest[k] != 0){ System.out.print((k-rest[k]) + " "); k = rest[k]; } System.out.println(k); return minNum[n]; } public static void main(String[] args) { int[] a = {1, 4, 5}; int n = 8; System.out.println(n + ": "); System.out.println(getChanges(a, n)); } }