Leetcode - Coin Change

My code:

public class Solution {
    private int min = -1;
    public int coinChange(int[] coins, int amount) {
        if (coins == null || coins.length == 0 || amount < 0)
            return -1;
        else if (amount == 0)
            return 0;
        Arrays.sort(coins);
        int ret = helper(coins, amount, coins.length - 1, 0);
        return (ret == Integer.MAX_VALUE ? -1 : ret);
    }
    
    private int helper(int[] coins, int amount, int end, int counter) {
        if (amount == 0)
            return counter;
        else if (amount < 0)
            return Integer.MAX_VALUE;
        else if (end < 0)
            return Integer.MAX_VALUE;
        int contain = helper(coins, amount - coins[end], end, counter + 1); // contain coins[end]
        int miss = helper(coins, amount, end - 1, counter); // miss coins[end]
        return Math.min(contain, miss);
    }
}

這個解法是超時的。
采用了 backtracing 的思想,復雜度達到了驚人的 O(2 ^ n)
其實這道題目原型不就是 combination 嗎?
combination 要求你返回所有組合,而這個,是讓你返回所有組合中size最小的。
看了答案后,正確解法如下,

My code:

public class Solution {
    public int coinChange(int[] coins, int amount) {
        if (coins == null || coins.length == 0 || amount < 0)
            return 0;
        if (amount == 0)
            return 0;
        int[][] dp = new int[amount + 1][coins.length]; // for getting amount i, using coins[] from 0 to j, how many coins
        for (int i = 0; i < dp[0].length; i++)
            dp[0][i] = 0;
        for (int i = 1; i < dp.length; i++) {
            for (int j = 0; j < dp[0].length; j++) {
                int contain = -1;
                if (i - coins[j] >= 0)
                    contain = (dp[i - coins[j]][j] == -1 ? -1 : dp[i - coins[j]][j] + 1);
                int miss = -1;
                if (j >= 1)
                    miss = dp[i][j - 1];
                if (contain == -1 && miss == -1)
                    dp[i][j] = -1;
                else if (contain == -1 || miss == -1)
                    dp[i][j] = (contain == -1 ? miss : contain);
                else
                    dp[i][j] = Math.min(contain, miss);
            }
        }
        return dp[dp.length - 1][dp[0].length - 1];
    }
}

采用backtracking 的方法,為什么復雜度會那么高?
因為里面有大量的重復的case,一次次地被再次訪問。

而使用了 dp, 將已經完成的結果全部存起來,下次要用的時候,直接訪問,就可以了。不用再次推導。這也是dp的意義。
**
將已經獲得的結果存起來,下次使用就不需要再花時間求了。避免了重復時間。
**

參考網頁:
http://www.geeksforgeeks.org/dynamic-programming-set-7-coin-change/

另外,這道題目要求的是最小coins組合,返回個數。
也可以求,達到target的所有組合個數。這樣, dp[i][j] 的含義,就是達到i,由coins[0] - [j],有多少種組合個數。 dp[0][j] = 1

也可以,再進一步,做成 combination.
Combination 復雜度是 O(2 ^ n)
而用這個dp,思路如下,
使用一個HashMap, key = i * row + j , which should be unique
value = its set for target.
那么,dp[i][j]時,
首先,
如果 dp[ i - coins[j] ] [j] = 0, 那么 (i, j) -> empty set
如果 dp[ i - coins[j] ] [j] > 0, 那么dp[i][j] = dp[ i - coins[j] ] [j] + 1,
然后,取出( ( i - coins[j] ) , j) 對應的set,深拷貝一份,插入 coins[ i ],
將整體插入到 (i, j) -> set 的HashMap 中。
我覺得是可行的。但可能會出現重復問題。

dp是個很大的問題,最近的理解在逐步加深。
去除掉重復情況,類似于一個HashMap

另外,HashSet, contains的復雜度,最壞情況可能是O(n)
HashSet 的底層是由 HashMap 實現的。

Anyway, Good luck, Richardo!

My code:

public class Solution {
    public int coinChange(int[] coins, int amount) {
        if (coins == null || coins.length == 0) {
            return -1;
        }
        if (amount == 0) {
            return 0;
        }
        
        Arrays.sort(coins);
        int[] dp = new int[amount + 1];
        for (int i = 0; i < coins.length; i++) {
            if (coins[i] <= amount) {
                dp[coins[i]] = 1;
            }
        }
        
        for (int i = 1; i <= amount; i++) {
            if (dp[i] != 0) {
                continue;
            }
            else {
                int j = coins.length - 1;
                int base = Integer.MAX_VALUE;
                for (; j >= 0; j--) {
                    if (coins[j] > i) {
                        continue;
                    }
                    else if (dp[i - coins[j]] == 0) {
                        continue;
                    }
                    else {
                        base = Math.min(base, 1 + dp[i - coins[j]]);
                    }
                }
                dp[i] = (base == Integer.MAX_VALUE ? 0 : base);
            }
        }
        
        return dp[amount] == 0 ? -1 : dp[amount];
    }
}

不知道為什么之前會用 二維DP來做?
這次做,直接就想到了一唯DP,并且寫了出來,雖然提交了好幾次。
這道題目和 perfect squares 很相似,都是用幾個給定的數,來湊這個數,求最小的次數。
我的思路是,對于不能湊出來的,就讓他的值保持0,
比如,
我們要湊一個 i
我們有 coins[],
那么 就
拿coins里面的一個個面值來試,
如果 dp[i - coins[i]] == 0, 那么就代表不能被湊出來,那么就換下一個面值。
如果可以被湊出來,求出最小的那個值。
dp[i] 表示的是, 湊錢到i需要的最少的個數。
O(amount * coins.length)
Anyway, Good luck, Richardo! -- 08/18/2016

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

推薦閱讀更多精彩內容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,775評論 0 33
  • 動態規劃(Dynamic Programming) 本文包括: 動態規劃定義 狀態轉移方程 動態規劃算法步驟 最長...
    廖少少閱讀 3,338評論 0 18
  • 個人學習批處理的初衷來源于實際工作;在某個迭代版本有個BS(安卓手游模擬器)大需求,從而在測試過程中就重復涉及到...
    Luckykailiu閱讀 4,781評論 0 11
  • 326. Power of Three Given an integer, write a function to...
    跑者小越閱讀 2,158評論 0 1
  • 像白云一樣的飄過來, 像流水一樣的流過來, 像蒲公英一樣的飛過來。 不需要濃妝艷抹, 只是淡雅高貴!! 像一陣清風...
    千劫已過閱讀 287評論 0 0