LeetCode每日之零錢兌換 II

零錢兌換 II (中等題)

給定不同面額的硬幣和一個總金額。寫出函數來計算可以湊成總金額的硬幣組合數。假設每一種面額的硬幣有無限個。
示例:
輸入: amount = 5, coins = [1, 2, 5]
輸出: 4
解釋: 有四種方式可以湊成總金額:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

先放解法:

//java
    public int change(int amount, int[] coins) {
        //動態規劃,dp[i]表示還要湊i塊錢有多少種拿法,所以初始化為dp[amount] = 1,也就是一個硬幣都不拿,還差amount的金額 就是唯一一種情況
        int[] dp = new int[amount + 1];
        dp[amount] = 1;
        //外層循環,取硬幣,每種硬幣取各種次數都記錄下來
        for (int coin : coins) {
            //此處請注意for循環的條件,走的方向是從大到小,想明白本題后請思考如果改成 從coin走到amount會怎樣
            for (int i = amount; i >= coin; i--) {
                //轉移方程取一個面值coin的硬幣,所以dp[i - coin]的情況數需要在原基礎上加上dp[i]的情況數
                dp[i - coin] += dp[i];
            }
        }
        return dp[0];
    }
//kotlin
//因剛開始學kotlin,所以語法使用不保證規范性和效率
    fun change(amount: Int, coins: IntArray): Int {
        val dp = IntArray(amount + 1)
        dp[amount] = 1
        for (coin in coins){
            for (i in amount downTo 0){
                if(i >= coin){
                    dp[i - coin] += dp[i]
                }
            }
        }
        return dp[0]
    }

思路(代碼含義在注釋里,以下僅為解題思路)

  1. 選擇dp,從經驗來說,求總次數的題目一般都是用dp,從原理來說,dp本質是基于已有情況往后遞推,而總次數的方案則都是符合dp的實現策略,而從個人來說,如果第一眼無法判斷是否使用dp,可以先看其他方案能否解決,因為dp的本質還是遍歷,取巧不行,只能走遍歷。
  2. 選定狀態,dp的難點就在于選擇狀態,狀態選對了,轉移方程很好寫,本題可用一維dp實現,狀態也只有金額這個值可以選了,我是個人習慣選了剩余金額,也可以選已選硬幣總金額,這里比較簡單,而需要用到二維dp的題目,例如昨天的每日盈利計劃(困難題)就需要決斷如何選狀態,具體之后寫,因為目前我也不是很明白怎么選,就是憑感覺選...
  3. 轉移方程,本題轉移方程也很容易dp[i - coin] += dp[i],也就是在dp[i](還需要湊的金額為i)的情況下取coin硬幣的情況數,再加上本身可能通過其他方式也能湊到dp[i-coin]的情況數。如果無法湊到金額i,也就是dp[i] = 0,那么dp[i-coin]就不增加
    其實也可以狀態定為目前已選的金額,而轉移方程可能也有變化,建議讀者自行嘗試寫一遍。

補充

題目取零錢是不考慮取錢順序的,所以無論怎么取,最終可以轉換為取x個一號幣,y個二號幣......都在上述循環中包含了進去


有趣的來了!!(重點必看)

如果題目改成 每個硬幣只能使用一次,如何處理?請在上面的基礎思考

解法(不信邪的選手可以寫幾個用例試試先...)

    public int change1(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[amount] = 1;
        for (int coin : coins) {
            //區別在這里 注釋部分為原解法
//          for (int i = amount; i >= coin; i--) {
            for (int i = coin; i <= amount; i++) {
                dp[i - coin] += dp[i];
            }
        }
        return dp[0];
    }

解釋

dp的思路是相同的,現在每個硬幣只能用一次,我們可以先思考為什么之前的解法每個硬幣能用多次
從第一次取硬幣開始,整個dp中只有dp[amount]=1,在這個基礎上我們取一號幣,讓dp[amount-coins[0]] 也為1,如果現在取兩個一號幣,也就是在dp[amount-coins[0]] 的基礎上再取一號幣,這就是每個硬幣能用多次的情況
現在再看看內層for循環,原解法是從后往前走,也就是在取了當前硬幣之后,能夠繼續取(也就是走了dp[i]之后,還能走到dp[i-coin[0]]),而新解法是從前往后走,走到第一次取一號幣的時候已經沒辦法回頭再取第二次一號幣了

優化

現在可以考慮優化,dp的優化不外乎就是空間時間
因為很多同學喜歡用多維dp,實際很多空間浪費,所以先考慮空間,這里空間沒得優化。
時間上我們可以看到外層循環是必要的,內存循環走了很多dp[i]為0的情況,所以可以考慮將內層循環的開始使用一個min記錄,記錄下每次產生的最小min使得dp[min] != 0,其實這里我是寫的時候就做了個優化,初始i = 0開始 優化為了i = coin開始,因為i小于coin時無法取,通過判斷直接去除
具體代碼我就不寫了還是比較簡單。

寫在最后

其實就是01背包的題目,為什么這里才說,我覺得其實這些題目本質是讓大家去思考問題,而不是思考能把什么模板(或者說解法)往上套,思考問題本身的意義遠大于解決這個題目

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 518 Coin Change 2 零錢兌換 II Description:You are given coins...
    air_melt閱讀 221評論 0 1
  • 題目 鏈接 給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算可以湊成總金額所需的最少...
    祁晏晏閱讀 193評論 0 0
  • 題目描述 零錢兌換 II 給定不同面額的硬幣和一個總金額。寫出函數來計算可以湊成總金額的硬幣組合數。假設每一種面額...
    一只可愛的檸檬樹閱讀 408評論 0 0
  • 零錢兌換 題目 給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算可以湊成總金額所需的...
    SunnyQjm閱讀 137評論 0 2
  • 表情是什么,我認為表情就是表現出來的情緒。表情可以傳達很多信息。高興了當然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,963評論 2 7