動態規划 硬幣問題


題目:有n種硬幣,面值分別為V1,V2,...Vn,每種都有無限多。給定非負整數S,可以選用多少個硬幣,使得面值之和恰好為S?輸出硬幣數目的最小值和最大值!

 

       如果我們有面值為1元、3元和5元的硬幣若干枚,如何用最少的硬幣湊夠11元? (表面上這道題可以用貪心算法,但貪心算法無法保證可以求出

解,比如1元換成2元的時候)

      首先我們思考一個問題,如何用最少的硬幣湊夠i元(i<11)?為什么要這么問呢? 兩個原因:1.當我們遇到一個大問題時,總是習慣把問題的規模變

小,這樣便於分析討論。 2.這個規模變小后的問題和原來的問題是同質的,除了規模變小,其它的都是一樣的, 本質上它還是同一個問題(規模變小后的

問題其實是原問題的子問題)。

        好了,讓我們從最小的i開始吧。當i=0,即我們需要多少個硬幣來湊夠0元。 由於1,3,5都大於0,即沒有比0小的幣值,因此湊夠0元我們最少需

要0個硬幣。 這時候我們發現用一個標記來表示這句“湊夠0元我們最少需要0個硬幣。

      那么, 我們用d(i)=j來表示湊夠i元最少需要j個硬幣。於是我們已經得到了d(0)=0, 表示湊夠0元最小需要0個硬幣。當i=1時,只有面值為1元的硬

幣可用, 因此我們拿起一個面值為1的硬幣,接下來只需要湊夠0元即可,而這個是已經知道答案的, 即d(0)=0。所以,d(1)=d(1-1)+1=d(0)+1=0+1=1。

當i=2時, 仍然只有面值為1的硬幣可用,於是我拿起一個面值為1的硬幣, 接下來我只需要再湊夠2-1=1元即可(記得要用最小的硬幣數量),而這個答案也

已經知道了。 所以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]);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM