你有三种硬币,分别面值2元,5元和7元,每种硬币都有足够多。买一本书需要27元。如何用最少的硬币组合正好付清,不需要对方找钱?
这道题是lintcode编号669的Coin Change问题。
实现方式有两个钟,一种是自底向上推导方法,另一种是自顶向下推导。
自底向上:
package leecode.daynamicprogramming;
public class CoinChange {
public static void main(String[] args) {
int[] a = {2,5,7};
int ret = coinChange(a, 9);
System.out.println(ret);
}
public static int coinChange(int[] A, int M) {
/*
* 原理:
* 1. 求一个给定值的最小数量硬币组合,可以将问题拆分成如下
*
* A = [2,5,7] // M = 27
* f[X] = min{f[X-2]+1, f[X-5]+1, f[X-7]+1}
* 他利用的原理是假设获得总额的组合,最后一个硬币币值是n, 总额是m, 那最小硬币数量一定是f(m) = f(m-n) + 1;
* 我们将最后一个硬币的币值全部枚举一遍,比如币值有2,5,7三种情况,总额27,这样我们可以获取三种可能,从中选一个最小值,就是我们要的
* 结果。
* 已给出示例为例,几种特殊情况:
* f(0)总是等于0
* f(1)无法得到货币组合,我们给出值是无穷大(程序中用Integer.MAX_VALUE代表)
* f(2)=1
* f(3)业务无法得到组合,所以也是无穷大
* 以此类推
*
*/
//f(x)代表给定硬币总额为x,求总和为x的最小硬币组合(硬币数量最少),这里用一个数组f代表。
// 每一个数组下标表示每一种总额, 对应的数组的值表示对应的组成该数额的硬币数量。
int[] f = new int[M + 1];
//边界条件: 总额为0,需要硬币的数量为0,所以f[0]=0。如果总额无论怎么用给定货币组合,都达不到,则f(x)=Integer.MAX_VALUE
f[0] = 0;
int n = A.length;
// 硬币的种类
// 初始化, 0个硬币 f[0] = 0;
// f[1], f[2], ... , f[27] = Integer.MAX_VALUE
//这里可以不显示指定f[0] = 0, jvm默认会给整形数组初始化为0
for (int i = 1; i <= M; i++) {
f[i] = Integer.MAX_VALUE;
}
for (int i = 1; i <= M; i++) {
// 使用第j个硬币 A[j] // f[i] = min{f[i-A[0]]+1, ... , f[i-A[n-1]]+1}
//从低到高,逐一计算f(x)的值,后边的值会利用前边算出的值。
for (int j = 0; j < n; ++j) {
// 如果通过放这个硬币能够达到重量i
/*f[i - A[j]] = Integer.MAX_VALUE表示最后一枚硬币是A[j],且总额减去该枚硬币的值,可能的硬币数量组合是.MAX_VALUE
* 的情况,也即总额减掉最后一枚币值A[j],用任何货币组合都无法实现,这种情况就没必要再更改f[i]的值了*/
if (i >= A[j] && f[i - A[j]] != Integer.MAX_VALUE) {
//在遍历每一个硬币的时候,f(i)可能会变化,总是获取当前f(i)和f(i-A[j])+1中最小的哪一个当作最新的f(i)的值
f[i] = Math.min(f[i - A[j]] + 1, f[i]);
}
}
}
if (f[M] == Integer.MAX_VALUE) {
return -1;
}
return f[M];
}
}
自底向上另一种写法:
public class Solution {
public int coinChange(int[] coins, int amount) {
int max = amount + 1;
int[] dp = new int[amount + 1];
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
自顶向下:
public class Solution {
public int coinChange(int[] coins, int amount) {
if (amount < 1) {
return 0;
}
return coinChange(coins, amount, new int[amount]);
}
private int coinChange(int[] coins, int rem, int[] count) {
if (rem < 0) {
return -1;
}
if (rem == 0) {
return 0;
}
if (count[rem - 1] != 0) {
return count[rem - 1];
}
int min = Integer.MAX_VALUE;
for (int coin : coins) {
int res = coinChange(coins, rem - coin, count);
if (res >= 0 && res < min) {
min = 1 + res;
}
}
count[rem - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
return count[rem - 1];
}
}