若是咱們有面值爲1元、3元和5元的硬幣若干枚,如何用最少的硬幣湊夠11元? (表面上這道題能夠用貪心算法,但貪心算法沒法保證能夠求出算法
解,好比1元換成2元的時候)數組
首先咱們思考一個問題,如何用最少的硬幣湊夠i元(i<11)?爲何要這麼問呢? 兩個緣由:1.當咱們遇到一個大問題時,老是習慣把問題的規模變spa
小,這樣便於分析討論。 2.這個規模變小後的問題和原來的問題是同質的,除了規模變小,其它的都是同樣的, 本質上它仍是同一個問題(規模變小後的code
問題實際上是原問題的子問題)。blog
好了,讓咱們從最小的i開始吧。當i=0,即咱們須要多少個硬幣來湊夠0元。 因爲1,3,5都大於0,即沒有比0小的幣值,所以湊夠0元咱們最少需it
要0個硬幣。 這時候咱們發現用一個標記來表示這句「湊夠0元咱們最少須要0個硬幣。class
那麼, 咱們用d(i)=j來表示湊夠i元最少須要j個硬幣。因而咱們已經獲得了d(0)=0, 表示湊夠0元最小須要0個硬幣。當i=1時,只有面值爲1元的硬im
幣可用, 所以咱們拿起一個面值爲1的硬幣,接下來只須要湊夠0元便可,而這個是已經知道答案的, 即d(0)=0。因此,d(1)=d(1-1)+1=d(0)+1=0+1=1。d3
當i=2時, 仍然只有面值爲1的硬幣可用,因而我拿起一個面值爲1的硬幣, 接下來我只須要再湊夠2-1=1元便可(記得要用最小的硬幣數量),而這個答案也static
已經知道了。 因此d(2)=d(2-1)+1=d(1)+1=1+1=2。
一直到這裏,你均可能會以爲,好無聊, 感受像作小學生的題目似的。由於咱們一直都只能操做面值爲1的硬幣!耐心點, 讓咱們看看i=3時的狀況。
當i=3時,咱們能用的硬幣就有兩種了:1元的和3元的( 5元的仍然沒用,由於你須要湊的數目是3元!5元太多了親)。 既然能用的硬幣有兩種,我就有兩
種方案。若是我拿了一個1元的硬幣,個人目標就變爲了: 湊夠3-1=2元須要的最少硬幣數量。即d(3)=d(3-1)+1=d(2)+1=2+1=3。 這個方案說的
是,我拿3個1元的硬幣;第二種方案是我拿起一個3元的硬幣, 個人目標就變成:湊夠3-3=0元須要的最少硬幣數量。即d(3)=d(3-3)+1=d(0)+1=0+1=1.
這個方案說的是,我拿1個3元的硬幣。好了,這兩種方案哪一種更優呢? 記得咱們但是要用最少的硬幣數量來湊夠3元的。因此, 選擇d(3)=1,怎麼來的呢?
具體是這樣獲得的:d(3)=min{d(3-1)+1, d(3-3)+1}。
上文中d(i)表示湊夠i元須要的最少硬幣數量,咱們將它定義爲該問題的」狀態」, 這個狀態是怎麼找出來的呢?我在另外一篇文章中寫過: 根據子問題定義狀
態。你找到子問題,狀態也就浮出水面了。 最終咱們要求解的問題,能夠用這個狀態來表示:d(11),即湊夠11元最少須要多少個硬幣。 那狀態轉移方程是什
麼呢?既然咱們用d(i)表示狀態,那麼狀態轉移方程天然包含d(i), 上文中包含狀態d(i)的方程是:d(3)=min{d(3-1)+1, d(3-3)+1}。沒錯, 它就是狀態
轉移方程,描述狀態之間是如何轉移的。固然,咱們要對它抽象一下,
d(i)=min{ d(i-vj)+1 },其中i-vj >=0,vj表示第j個硬幣的面值;
有了狀態和狀態轉移方程,這個問題基本上也就解決了。固然了,Talk is cheap,show me the code!
/** * 硬幣找零 * * @author sun * */ public class MinCoins { public static void main(String[] args) { int[] coins = { 1, 3, 5 }; int value = 11; CoinDp(value, coins); } public static void CoinDp(int n, int[] coinValue) { int count = 0;// 記錄執行次數 int[] min = new int[n + 1]; // 用來存儲獲得n塊錢須要的硬幣數的最小值 min[0] = 0; for (int i = 1; i <= n; i++) { min[i] = Integer.MAX_VALUE;// 初始化數組中的每一個值都是最大的整數 for (int j = 0; j < coinValue.length; j++) { count++; if (i >= coinValue[j] && min[i] > min[i - coinValue[j]] + 1) { min[i] = min[i - coinValue[j]] + 1; } } System.out.println("獲取" + i + "塊錢,最少須要的硬幣數:" + min[i] + ",執行的次數:" + count); } System.out.println(min[n]); } }