動態規划求解最多有幾種方案求解硬幣找零問題


一,問題描述

假設有 m 種面值不同的硬幣,存儲在 coinsValues數組中,現需要使用這些硬幣來找錢,各種硬幣的使用個數不限。 求對於給定的錢數N,我們最多有幾種不同的找錢方式。硬幣的順序並不重要。

 

二,動態規划分析

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

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

第 i 枚硬幣     面值

    1                1

    2                3

    3                4

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

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

設 c[i,j]表示 使用 第 1,2,...i 種面值的硬幣時,需要找金額為 j 的錢,最多可采用多少種不同的方式?

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

①最優子結構

對於某種面值的硬幣,要么使用了(可能使用多次)它,要么不使用它。故:

c[i,j]=c[i-1,j] + c[i,j-coinsValue[i]]

c[i-1,j] 表示不使用第 i 枚硬幣, c[i, j-coinsValue[i]] 表示至少使用了一次 第 i 枚硬幣。c[i, j-coinsValue[i]] 表示,第 i 枚硬幣還可以繼續使用。因為第一個參數還是 i

從這里可以看出:用到了《組合數學》中的加法原理。

 

如何確定初始(基准)條件?一個重要的方法就是畫一個簡單的實例圖。(借用網上一張圖:)

C({1,2,3},j) --> recursiveChargeTypes
                              C({1,2,3}, 5)                     
                           /                \
                         /                   \              
             C({1,2,3}, 2)                 C({1,2}, 5)
            /     \                        /         \
           /        \                     /           \
C({1,2,3}, -1)  C({1,2}, 2)        C({1,2}, 3)    C({1}, 5)
               /     \            /    \            /     \
             /        \          /      \          /       \
    C({1,2},0)  C({1},2)   C({1,2},1) C({1},3)    C({1}, 4)  C({}, 5)
                   / \      / \       / \        /     \    
                  /   \    /   \     /   \      /       \ 
                .      .  .     .   .     .   C({1}, 3) C({}, 4)
                                               /  \
                                              /    \  
                                             .      .

比如,按照紅色那條路走,就知道 5 使用了硬幣面值3 和 2,故成功找零,此時 j=0了,這是一種找零方式 ==》 當j==0時,返回1

 

三,代碼實現

public class DPCoinCharge {
    
    public static int chargeTypes(int[] coinsValues, int n){
        int m = coinsValues.length;
        int[][] c = new int[m+1][n+1];
        
        //基准條件,可參考下面的遞歸代碼
        for(int i = 0; i <=m; i++)
            c[i][0] = 1;
        for(int i = 1; i <=n; i++)
            c[0][i] = 0;
        
        
        for(int i = 1; i <=m; i++)
        {
            for(int j = 1; j <=n; j++)
            {
                if(j < coinsValues[i-1])//第 i 枚硬幣 不可用. (需要找 5塊錢,但是現在只有一張百元大鈔)
                {
                    c[i][j] = c[i-1][j];
                    continue;
                }
                //在第 i 枚硬幣可用的情況下, 不使用 第 i 枚硬幣 或者第 i 枚硬幣至少使用一次---狀態方程
                c[i][j] = c[i-1][j] + c[i][j - coinsValues[i-1]];//coinsValues下標從0開始
            }
        }
        return c[m][n];
    }
    
    //遞歸實現
    public static int recursiveChargeTypes(int[] coinsValues, int m, int n)
    {
        //基准條件 可以 通過畫一個簡單的實例 分析來得出. 比如 recursiveChargeTypes({1,3,4}, 3, 5)
        if(n == 0)
            return 1;
        if(n < 0)
            return 0;
        if(m <= 0)
            return 0;
        else
            return recursiveChargeTypes(coinsValues, m-1, n) + recursiveChargeTypes(coinsValues, m, n-coinsValues[m]);
    }
    
    public static void main(String[] args) {
        int[] coinsValues = {1,2,3};
        int n = 5;
        int maxTypes = chargeTypes(coinsValues, n);
        System.out.println(maxTypes);
    }
}

 

四,參考資料

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

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

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

http://www.acmerblog.com/dp6-coin-change-4973.html


免責聲明!

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



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