算法-回溯法/動態規劃-零錢兌換

算法-回溯法/動態規劃-零錢兌換

1 題目概述

1.1 題目出處

https://leetcode-cn.com/problems/coin-change/

1.2 題目描述

給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1。

示例 1:

輸入: coins = [1, 2, 5], amount = 11
輸出: 3
解釋: 11 = 5 + 5 + 1
示例 2:

輸入: coins = [2], amount = 3
輸出: -1

說明:
你可以認爲每種硬幣的數量是無限的。

2 回溯法

2.1 思路

首先將零錢從小到大排序,然後貪心地優先放面額大的零錢,直到超過目標值就回溯;或等於目標值,就比較當前零錢總數和最小值,如果更小就設爲最小值,然後繼續回溯。

2.2 代碼

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(coins == null || coins.length == 0){
            return -1;
        }
        Arrays.sort(coins);
        return backtrack(coins, amount, 0, 0, coins.length - 1);
    }

    int min = Integer.MAX_VALUE;

    private int backtrack(int[] coins, int amount, int count, int sum, int max){
        if(sum > amount){
            // 結束條件
            return -1;
        }
        if(sum == amount){
            // 結束條件
            return count;
        }
        for(int i = max; i >= 0; i--){
            // 選擇
            sum += coins[i];
            int result = backtrack(coins, amount, count + 1, sum, i);
            if(result > -1){
                if(result < min){
                    min = result;
                }
            }
            sum -= coins[i];
        }
        if(min == Integer.MAX_VALUE){
            return -1;
        }else{
            return min;
        }
    }
}

2.3 時間複雜度

在這裏插入圖片描述
時間複雜度太高,直接崩了

3 回溯法-剪枝

3.1 思路

前面回溯法,相當於窮舉所有組合,所有超時也是在情理之中。

但由於可能出現以下情況:
對於這個測試用例

[13,12,11,9,61]
33

優先找到:
13 13 6 1
然後可以找到
13 12 6 1 1
還可以找到
13 11 9(最優解)

這說明,我們必須遞歸完這些解,否則可能無法找到全局最優解!

但是我們還是用了剪枝思想,見代碼註釋中的k + cnt < res

3.2 代碼

class Solution {
    private int min = Integer.MAX_VALUE;

    public int coinChange(int[] coins, int amount) {
        if(coins == null || coins.length == 0){
            return -1;
        }
        Arrays.sort(coins);
        backtrack(coins, amount, 0, coins.length - 1);
        return min == Integer.MAX_VALUE ? -1 : min;
    }
    
    private void backtrack(int[] coins, int amount, int count, int max){
        if(amount == 0){
            // 結束條件
            min = Math.min(min, count);
            return;
        }

        if(max < 0){
            // 結束條件
            return;
        }

        // k + count < min是剪枝關鍵
        // 考慮 k + count == min,此時已經不需要再繼續查找,因爲更小面額肯定需要的數量更多,而相等數量的組合已經有了
        // 考慮 k + count > min,此時也已經不需要再繼續查找,更小面額組合鈔票數肯定更大於當前 k + count

        for(int k = amount / coins[max]; k >= 0 && (k + count < min) ; k--){
            backtrack(coins, amount - k * coins[max], count + k, max - 1);
        }
    }
}

3.3 時間複雜度

在這裏插入圖片描述
這次好很多!

4 動態規劃

4.1 思路

設dp[i]表示金額i的最小硬幣個數,則

dp[i] = Math.min(dp[i-coins[0]] + 1, dp[i-coins[2]] + 1,...,dp[i-coins[n-1]] + 1);

4.2 代碼

class Solution {
    private int min = Integer.MAX_VALUE;

    public int coinChange(int[] coins, int amount) {
        if(coins == null || coins.length == 0 || amount < 0){
            return -1;
        }
        if(amount == 0){
            return 0;
        }

        // 將硬幣從小到大排序,當目標金額小於某個硬幣面值時可直接排除大面額硬幣
        Arrays.sort(coins);

        // 設dp[i]表示金額i的最小硬幣個數
        // 則dp[i] = Math.min(dp[i-coins[0]] + 1, dp[i-coins[2]] + 1,...,dp[i-coins[n-1]] + 1);
        int[] dp = new int[amount + 1];
        dp[0] = 0;
        // 記錄用到的最大硬幣下標
        int max = coins.length - 1;
        // 更新用到的最大硬幣下標,對一個硬幣剛好能組成的金額進行dp[i]初始化爲1
        for(int i = 0; i < coins.length; i++){
            if(amount == coins[i]){
                return 1;
            }
            if(coins[i] > amount){
                max = i - 1;
                break;
            }
            dp[coins[i]] = 1;
        }
        // 從1到amout開始動態規劃過程
        for(int i = 1; i <= amount; i++){
            // 該金額對應的最小硬幣個數
            int min = Integer.MAX_VALUE;
            for(int j = 0; j <= max; j++){
                if(coins[j] > i){
                    // 如果硬幣面額比當前金額大,就停止當前趟查找最小硬幣個數
                    break;
                }
                int tmp = dp[i-coins[j]];
                if(tmp == -1){
                    // 該金額構成肯定不包含當前硬幣
                    continue;
                }
                min = Math.min(min, tmp);
            }
            // min + 1 表示要加上一個硬幣組成當前金額
            dp[i] = min == Integer.MAX_VALUE? -1 : (min + 1); 
        }
        return dp[amount];
    }
}

4.3 時間複雜度

在這裏插入圖片描述

4.4 空間複雜度

O(amount)

參考文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章