问题:如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元?
此题容易让人在动态规划和贪心算法之间徘徊,毕竟对于初学者来说这两种算法要熟练掌握还是需要时间和实践的。
首先可以肯定的回答:贪心算法是行不通的。如果使用贪心算法,肯定是每次找最大的然后找次小的,如果不行再找次小的。
上述例子其实可以5元两个,1元一个共三个硬币凑够11元。但是如果输入是这种呢:5,4,1凑够8。如果用贪心则需要5,1,1,1,但实际上可以两个4元就搞定。
所以需要使用动态规划如下:
假设d(i)为凑够i元所需最少硬币数,则
d(0) = 0 理所当然
d(1) = 1 要凑够1元,需要从面值小于等于1元的硬币中选择,目前只有面值为1元的硬币
此时d(1) = d(0) + 1
d(2) = d(2 - 1) + 1 = 2, 从面值小于等于2元的硬币中选择,符合要求的硬币面值为:1元。
此时d(2) = d(2-1) + 1
d(3) = d(3 - 3) + 1 = 1, 从面值小于等于3元的硬币中选择,符合要求的硬币面值为:1元,3元。
此时有有两种选择:是否选择含有面值3元的硬币
含有3元硬币:d(3) = d(3 - 3) + 1 = 1
不含3元硬币:d(3) = d(3 - 1) + 1 = d(2) + 1 = 3
自然是选择二者中较小值
那这里我们加上的是哪个硬币呢。嗯,其实很简单,把每个硬币试一下就行了:
假设最后加上的是 1 元硬币,那 d(i) = d(j) + 1 = d(i - 1) + 1。
假设最后加上的是 3 元硬币,那 d(i) = d(j) + 1 = d(i - 3) + 1。
假设最后加上的是 5 元硬币,那 d(i) = d(j) + 1 = d(i - 5) + 1。
我们分别计算出 d(i - 1) + 1,d(i - 3) + 1,d(i - 5) + 1 的值,取其中的最小值,即为最优解,也就是 d(i)
static int[] coins = {1,3,5};
public static void main(String[] args) {
int sum = 11;//凑够11元
int[] d = new int[sum+1];
findmin(0,sum,d);
for (int i = 0; i <= sum; i++) {
System.out.println("凑齐 " + i + " 元需要 " + d[i] + " 个硬币");
}
System.out.println();
find_min(sum,d);
}
private static void find_min(int sum, int[] d) {
d[0] = 0;
for (int i = 1; i <= sum; i++) {
int min = i;//最大个数不会超过面币价值数
for (int coin: coins) {
if(i>=coin && d[i-coin] + 1 < min)
{
min = d[i-coin]+1;
}
}
d[i] = min;
}
for (int i = 0; i < d.length; i++) {
System.out.print(d[i]+" ");
}
}
如果要记录是哪几个硬币可以加上一个数组用来记录每次更新时候的硬币种类。如下:
private static void FindMin(int money, int[] coin) {
int n = coin.length;
int[] coins = new int[money+1];//存储1...money找零最少需要的硬币的个数
int[] coinValue = new int[money+1];//最后加入的硬币,方便后面输出是哪几个硬币
coinNum[0] = 0;
for (int i = 1; i <=money; i++) {
int minNum = i;//i面值钱,最少需要硬币个数
int usedMoney = 0;//
for (int j = 0; j < n; j++) {
if(i>=coin[j])
{
System.out.print(coinValue[i-coin[j]]+" "+i+" "+coin[j]);
if(coinNum[i-coin[j]]+1<=minNum)
{
minNum = coinNum[i-coin[j]]+1;//更新
usedMoney = coin[j];
}
System.out.println();
}
}
coins[i] = minNum;
coinValue[i] = usedMoney;
}