硬幣找零問題的動態規划實現


一,問題描述

給定一組硬幣數,找出一組最少的硬幣數,來找換零錢N。

比如,可用來找零的硬幣為: 1、3、4   待找的錢數為 6。用兩個面值為3的硬幣找零,最少硬幣數為2。而不是 4,1,1

因此,總結下該問題的特征:①硬幣可重復多次使用。②在某些情況下,該問題可用貪心算法求解。具體可參考:某種 找換硬幣問題的貪心算法的正確性證明

 

二,動態規划分析

為了更好的分析,先對該問題進行具體的定義:將用來找零的硬幣的面值存儲在一個數組中。如下:

coinsValues[i] 表示第 i 枚硬幣的面值。比如,

第 i 枚硬幣     面值

    1                1

    2                3

    3                4

待找零的錢數為 n (上面示例中 n=6)

為了使問題總有解,一般第1枚硬幣的面值為1

考慮該問題的最優子結構:設 c[i,j]表示 可用第 0,1,.... i 枚硬幣 對 金額為 j 的錢 進行找錢 所需要的最少硬幣數。

i 表示可用的硬幣種類數, j 表示 需要找回的零錢

第 i 枚硬幣有兩種選擇:用它來找零 和 不用它找零。因此,c[i,j]的最優解如下:

c[i,j]= min{c[i-1,j] , c[i, j-coinsValues[i]] + 1}   其中,

c[i-1,j] 表示 使用第 i 枚硬幣找零時,對金額為 j 進行找錢所需要的最少硬幣數

c[i, j-coinsValues[i]] + 1 表示 使用 第 i 枚硬幣找零時,對金額為 j 進行找錢所需要的最少硬幣數。由於用了第 i 枚硬幣,故使用的硬幣數量要增1

c[i,j] 取二者的較小值的那一個。

另外,對特殊情況分析(特殊情況1)一下:

c[0][j]=Integer.MAXVALUE ,因為 對 金額為 j 的錢找零,但是可用的硬幣面值 種類為0,這顯然是無法做到的嘛(除非是強盜:) )

其實這是一個”未定義“的狀態。它之所以初始為Integer.MAXVALUE,與《背包九講》中的”當要求背包必須裝滿的條件下,價值盡可能大“時 的初始化方式一樣。

 

c[i][0]=0,因為,對 金額為0的錢 找零,可用來找零的硬幣種類有 i 種,金額為0啊,怎么找啊,找個鴨蛋啊。

其實,邊界條件是根據具體的問題而定的。DP太博大了,理解的不深。

 

另外,還有一種特殊情況(特殊情況2),就是: coinsValues[i] > j

這說明第 i 枚硬幣的面值大於 金額 j ,這也不能用 第 i 枚硬幣來找零啊,(欠你5塊錢,但是找你一張百元大鈔)不然就虧了了(吃虧是福啊^~^...or 殺雞焉用牛刀!!!)

有了這個特征轉移方程:c[i,j]= min{c[i-1,j] , c[i, j-coinsValues[i]] + 1}  ,就好寫代碼了。

 

三,JAVA代碼實現

 1     /**
 2      * 
 3      * @param coinsValues 可用來找零的硬幣 coinsValues.length是硬幣的種類
 4      * @param n 待找的零錢
 5      * @return 最少硬幣數目
 6      */
 7     public static int charge(int[] coinsValues, int n){
 8         int[][] c = new int[coinsValues.length + 1][n + 1];
 9         
10         //特殊情況1
11         for(int i = 0; i <= coinsValues.length; i++)
12             c[i][0] = 0;
13         for(int i = 0; i <= n; i++)
14             c[0][i] = Integer.MAX_VALUE;
15         
16         for(int j_money = 1; j_money <=n; j_money++)
17         {
18             
19             for(int i_coinKinds = 1; i_coinKinds <= coinsValues.length; i_coinKinds++)
20             {
21                 if(j_money < coinsValues[i_coinKinds-1])//特殊情況2,coinsValues數組下標是從0開始的,  c[][]數組下標是從1開始計算的
22                 {
23                     c[i_coinKinds][j_money] = c[i_coinKinds - 1][j_money];//只能使用 第 1...(i-1)枚中的硬幣
24                     continue;
25                 }
26                 
27                 //每個問題的選擇數目---選其中較小的
28                 if(c[i_coinKinds - 1][j_money] < (c[i_coinKinds][j_money - coinsValues[i_coinKinds-1]] +1))
29                     c[i_coinKinds][j_money] = c[i_coinKinds - 1][j_money];
30                 else
31                     c[i_coinKinds][j_money] = c[i_coinKinds][j_money - coinsValues[i_coinKinds-1]] +1;
32             }
33         }
34         return c[coinsValues.length][n];
35     }

 ①第28行-20行 就是狀態轉換方程的表示。

②第16行-第19行的for循環體現就是動態規划的自底向上的思想。

復雜度分析:從代碼19-20行的for循環來看,時間復雜度為O(MN),M為可用的硬幣種類數目,N為待找的零錢金額

從理論上分析,DP(Dynamic Programming)的時間復雜度為子問題的個數乘以每個子問題的可用選擇數。顯然,這個有MN個子問題,每個子問題有兩種選擇(選第i枚硬幣和不選第i枚硬幣)。

一直很好奇DP通過列一個方程就把一個問題給解決了,其實從16-19行的for循環來看,循環的下標是由小到大,說明它先解決子問題,然后再把原問題給解決了。

 

四,參考資料

硬幣找零問題的動態規划實現

某種 找換硬幣問題的貪心算法的正確性證明

從 活動選擇問題 看動態規划和貪心算法的區別與聯系

http://haolloyin.blog.51cto.com/1177454/352115

 

五,完整代碼

import java.util.Arrays;

public class DPCharge {
    
    /**
     * 
     * @param coinsValues 可用來找零的硬幣 coinsValues.length是硬幣的種類
     * @param n 待找的零錢
     * @return
     */
    public static int charge(int[] coinsValues, int n){
        int[][] c = new int[coinsValues.length + 1][n + 1];
        
        //特殊情況1
        for(int i = 0; i <= coinsValues.length; i++)
            c[i][0] = 0;
        for(int i = 0; i <= n; i++)
            c[0][i] = Integer.MAX_VALUE;
        
        for(int j_money = 1; j_money <=n; j_money++)
        {
            for(int i_coinKinds = 1; i_coinKinds <= coinsValues.length; i_coinKinds++)
            {
                if(j_money < coinsValues[i_coinKinds-1])//特殊情況2
                {
                    c[i_coinKinds][j_money] = c[i_coinKinds - 1][j_money];
                    continue;
                }
                
                //每個問題的選擇數目---選其中較小的
                if(c[i_coinKinds - 1][j_money] < (c[i_coinKinds][j_money - coinsValues[i_coinKinds-1]] +1))
                    c[i_coinKinds][j_money] = c[i_coinKinds - 1][j_money];
                else
                    c[i_coinKinds][j_money] = c[i_coinKinds][j_money - coinsValues[i_coinKinds-1]] +1;
            }
        }
        return c[coinsValues.length][n];
    }
    
    public static void main(String[] args) {
        int[] coinsValues = {1,3,4};
        Arrays.sort(coinsValues);//需要對數組排序,不然會越界.....
        int n = 6;
        int minCoinsNumber = charge(coinsValues, n);
        System.out.println(minCoinsNumber);
    }
}

 

原文:http://www.cnblogs.com/hapjin/p/5578852.html


免責聲明!

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



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