硬幣組合問題之最少硬幣個數
問題:如果我們有面值為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; }