硬幣找零問題是動態規劃的一個經典問題,其中最少硬幣找零是一個變種,本篇將參照上一篇01揹包問題的解題思路,來詳細講解一下最少硬幣找零問題。若是你須要查看上一篇,能夠點擊下面連接: 詳解動態規劃01揹包問題--JavaScript實現 數組
也能夠查看下一篇 詳解動態規劃最長公共子序列--JavaScript實現瀏覽器
下面讓咱們開始吧。bash
給定4種面額的硬幣1分,2分,5分,6分,若是要找11分的零錢,怎麼作才能使得找的硬幣數量總和最少。post
最少硬幣找零問題,是爲了求硬幣的組合,因此一個大前提是硬幣無限量供應。咱們創建以下表格來分析問題: ui
其中每列用j表示零錢總額,每行i表示硬幣面額。T[i][j]
表示硬幣個數,它是咱們即將填入表格的數字。spa
在填寫表格以前,咱們須要先明確幾個規則:3d
當咱們只能使用面額爲1分的硬幣時,根據上面的規則,那麼很顯然,總額爲幾分,就須要幾個硬幣。即T[i][j] = j
。code
當咱們有1分和2分兩種面額時,那麼組合方式就相對多了點。cdn
i=1 j = 1
:總額爲1時,只能使用1分的面額。即填1。 i=1 j = 2
:總額爲2時,可使用2個1分的,也可使用1個2分的。由於咱們要求最少硬幣,因此使用1個2分的。表格所表達的意思是硬幣的數量,因此這裏也填1。 i=1 j = 3
:總額爲3時,可使用3個1分的,也可使用1個1分加1個2分。所以這裏應該填2。 i=1 j = 4
:總額爲4時,可使用4個1分的,可使用2個1分加1個2分,也可使用2個2分。其中硬幣最少的狀況應該是2個2分。所以這裏填2。 i=1 j = 5
:總額爲5時,組合就更多了,可是聰明的你應該能想到使用2個2分加1個1分,能夠實現最少硬幣的需求。所以這裏填3。blog
咱們來看填寫完上面5格後的狀況:
建議你本身再紙上照着我這圖畫一個表格。接下來,別急着填表。咱們要根據已有的數據,總結出T[i][j]
的規律,而後經過填寫剩餘表格來驗證。
咱們將硬幣面額使用數組coins[i]來表示,根據表格有 1分=coins[0], 2分=coins[1]。 當j<coins[i]時,T[i][j]
的值,應該等於它的同列,上一行,即便T[i][j] == T[i-1][j]
。 好比咱們從表中所看到的,T[1][1]==T[0][1]
。 當j>=coins[i]時,根據已有的 i=1行能夠推出一個規律,令a = 1+T[i][j-coins[i]]
,T[i][j]= min(T[i-1][j],a)
,即兩者比較取最小值。可能一開始你看到這個關於a的公式,有點太忽然,難以接受。稍微解釋一下,當第i行,優先選擇這同樣的硬幣,由於這一行的硬幣面額最大,最有可能使得總硬幣數量最少。所以j-coins[i]
,就很好理解了,就是選擇了這一行的硬幣後,還剩下多少總額。舉個例子,當i=1,j=3時,j-coins[1]=1。那麼選擇2分後,還剩餘總額爲1,這時候咱們再定位到i=1,j=1,即T[1][1],它的值爲1,再加上一個常數1,即得最終結果2。
再舉例,i=1 j=5
。因爲是從左到右填表的,因此i=1,j<5的表格都填完了。j-coins[i]=3,定位到T[1][3]=2
,加上常數1,即得最後結果T[1][5]=3
。
其實公式自己很短,也很好記。若是實在沒法理解,建議先不用糾結。先最小化瀏覽器,不要看本篇剩餘的內容。帶着這個解題公式,本身在紙上,把這個表格填寫完整,在填表分析的過程當中就能慢慢理解了。
按照上一步所提供的公式,其實全部的T[i][j]
均可以填完了。以下表格。
建議先本身再紙上填表,填完了,再和個人圖對比一下,看是否答案存在出入。
以上的填表邏輯,使用僞代碼表示以下
if(i == 0){
T[i][j] = j/coins[i]; //硬幣找零必定要有個 最小面額1,不然會無解
}else{
if(j >= coins[i]){
T[i][j] = min(T[i-1][j],1+T[i][j-coins[i]])
}else{
T[i][j] = T[i-1][j];
}
}
複製代碼
至此,填完表格咱們已經接近完成了。接下來要尋找從表格中尋找硬幣組合。🤔
與填表順序相反,尋找組合從有下角開始。
首先須要明確的是若是T[i][j] == T[i-1][j]
,那麼就向上搜索。根據圖來分析:
1. 定位到T[3][11]
,因爲不存在T[i][j] == T[i-1][j]
,因此不用向上搜索,肯定選中一個6分
硬幣。尋找組合的思路和填寫T[i][j]的思路幾乎是反過來的。
2. 選擇一個6分硬幣後,剩餘的總額爲11-6=5。所以定位到T[3][5]中。因爲T[3][5]==T[2][5],所以看圖中的藍色箭頭,向上搜索,直到T[i][j] != T[i-1]
。
3. 定位到T[2][5]中,此時coins[i]爲5分。選中5分硬幣只有,剩餘的總額爲5-5=0。
4. 當j=0時,搜索結束。由上面步驟肯定選中的硬幣組合爲:1個5分,1個6分。
以上就是整個最少硬幣找零問題的分析思路。最終代碼使用 JavaScript 實現,若是你的 Sublime 支持純 JavaScript,你能夠直接複製黏貼代碼,command + b 直接運行查看結果,而後修改輸入變量,查看更多狀況下的輸出結果。
//動態規劃 -- 硬幣找零問題
function minCoins(coins,total,n){
var T = [];
for(let i = 0;i<n;i++){
T[i] = []
for (let j=0;j<= total;j++){
if(j == 0){
T[i][j] = 0;
continue;
}
if(i == 0){
T[i][j] = j/coins[i]; //硬幣找零必定要有個 最小面額1,不然會無解
}else{
if(j >= coins[i]){
T[i][j] = Math.min(T[i-1][j],1+T[i][j-coins[i]])
}else{
T[i][j] = T[i-1][j];
}
}
}
}
findValue(coins,total,n,T);
return T;
}
function findValue(coins,total,n,T){
var i = n-1, j = total;
while(i>0 && j >0){
if(T[i][j]!=T[i-1][j]){
//鎖定位置,肯定i,j值,開始找構成結果的硬幣組合。 其實根據這種計算方法,只須要考慮最右邊那一列,從下往上推。
//console.log(T[i][j]);
break
}else{
i--;
}
}
var s = []; //存儲組合結果
while(i >= 0 && j > 0 ){
s.push(coins[i]);
j=j-coins[i];
if(j <= 0){
break; //計算結束,退出循環
}
//若是 i == 0,那麼就在第 0 行一直循環計算,直到 j=0便可
if(i>0){
//console.log(i);
while(T[i][j] == T[i-1][j]){
i--;
if(i== 0){
break;
}
}
}
}
console.log(s);
//能夠把數組s return 回去
}
var coins = [1,2,5,6];
var total = 11
var n = coins.length
console.log(minCoins(coins,total,n));
複製代碼