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

一,問題描述html

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

好比,可用來找零的硬幣爲: 一、三、4   待找的錢數爲 6。用兩個面值爲3的硬幣找零,最少硬幣數爲2。而不是 4,1,1算法

所以,總結下該問題的特徵:①硬幣可重複屢次使用。②在某些狀況下,該問題可用貪心算法求解。具體可參考:某種 找換硬幣問題的貪心算法的正確性證實數組

 

二,動態規劃分析post

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

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

第 i 枚硬幣     面值htm

    1                1blog

    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

相關文章
相關標籤/搜索