詳解動態規劃最少硬幣找零問題--JavaScript實現

硬幣找零問題是動態規劃的一個經典問題,其中最少硬幣找零是一個變種,本篇將參照上一篇01揹包問題的解題思路,來詳細講解一下最少硬幣找零問題。若是你須要查看上一篇,能夠點擊下面連接: 詳解動態規劃01揹包問題--JavaScript實現 數組

也能夠查看下一篇 詳解動態規劃最長公共子序列--JavaScript實現瀏覽器

下面讓咱們開始吧。bash

問題

給定4種面額的硬幣1分,2分,5分,6分,若是要找11分的零錢,怎麼作才能使得找的硬幣數量總和最少。post

分析

最少硬幣找零問題,是爲了求硬幣的組合,因此一個大前提是硬幣無限量供應。咱們創建以下表格來分析問題: ui

其中每列用j表示零錢總額,每行i表示硬幣面額。T[i][j]表示硬幣個數,它是咱們即將填入表格的數字。spa

在填寫表格以前,咱們須要先明確幾個規則:3d

  • 當填寫第i行時,使用的硬幣面額僅能是i以及小於i的面額。舉個例子,好比我填寫第0行,i=0,那麼這同樣只能使用面額爲1分的硬幣。當我填寫第2行,i=2,那麼可使用1分,2分,5分三種面額的硬幣。
  • 當填寫第j列時,表示當前須要使用硬幣湊出的總額。好比j=6,表示須要使用硬幣組合出總額爲6分的狀況。

1. i = 0

當咱們只能使用面額爲1分的硬幣時,根據上面的規則,那麼很顯然,總額爲幾分,就須要幾個硬幣。即T[i][j] = jcode

2. i = 1

當咱們有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

其實公式自己很短,也很好記。若是實在沒法理解,建議先不用糾結。先最小化瀏覽器,不要看本篇剩餘的內容。帶着這個解題公式,本身在紙上,把這個表格填寫完整,在填表分析的過程當中就能慢慢理解了。

3. 剩餘內容

按照上一步所提供的公式,其實全部的T[i][j]均可以填完了。以下表格。

建議先本身再紙上填表,填完了,再和個人圖對比一下,看是否答案存在出入。

4.僞代碼

以上的填表邏輯,使用僞代碼表示以下

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];
	}
}
複製代碼

5. 尋找組合

至此,填完表格咱們已經接近完成了。接下來要尋找從表格中尋找硬幣組合。🤔

與填表順序相反,尋找組合從有下角開始。

首先須要明確的是若是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));
複製代碼

相關文章
相關標籤/搜索