算法學習——動態規划講解


一、概念

通過把原問題分解為相對簡單的子問題的方式求解復雜問題的方法。動態規划常常適用於有重疊子問題和最優子結構性質的問題。

二、題型特點

  • 計數
    • 有多少種方式走到最右下角
  • 求最大值最小值
    • 從左上角走到右下角的最大數字和
  • 求存在性
    • 能否選出k個數使得和為sum

三、如何使用動態規划

這里先看一道LeetCode題。從這道題來學習如何使用動態規划。

題目鏈接LeetCode-322

Coin Change

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

示例

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

該題是一個求最大最小的動態規划算法題。

與遞歸解法相比,沒有重復計算。

3.1 組成部分一:確定狀態

確定狀態需要有兩個注意的點:最后一步子問題

1.最后一步

肯定是\(k\)枚硬幣加起來等於11。最后一枚硬幣值假設是\(a_k\),則剩下的\(k-1\)枚硬幣的值為\(11-a_k\)

由於是最優解,則11-\(a_k\)的硬幣數一定是最少。

2.子問題

將原問題轉換為子問題,最少用多少枚硬幣拼出\(11-a_k\)

那么\(a_k\)到底是多少,因為有3枚硬幣,所以只可能是1、2、5中的一個。

子問題方程如下:

\(f(11) = min\{f(11-1)+1,f(11-2)+1,f(11-5)+1\}\)

f(11)為拼出面值為11所需的最少硬幣數。

根據以上,使用遞歸的解法:


public class Dp1 {

    public int getMinCoin(int X) {

        if (X == 0) return 0;
        int res = 10000;
        if (X >= 1) {
            res =  Math.min(getMinCoin(X - 1)+1, res);
        }
        if (X >= 2) {
            res =  Math.min(getMinCoin(X - 2)+1, res);
        }
        if (X >= 5) {
            res =  Math.min(getMinCoin(X - 5)+1, res);
        }
        return res;
    }

    public static void main(String[] args) {
        Dp1 dp1 = new Dp1();
        int result = dp1.getMinCoin(11);
        System.out.println(result);
    }

}

在這里插入圖片描述
使用遞歸來解決,有比較多的重復計算,效率比較低。

動態規划會保存計算結果,來避免遞歸重復計算的問題。

3.2 組成部分二:轉移方程

動態規划的解法

狀態f[X]表示,面值為X所需的最小硬幣數。
對於任意的X,滿足
\(f[X] = min\{f[X-1]+1,f[X-2]+1,f[X-5]+1\}\)

意思就是獲取面值大小為X最少需要的硬幣數 = 從(最后一個硬幣選1時,剩下的要湊面值為X-1所需要的最少硬幣數,因為最后一個硬幣選了1,所以硬幣數要+1,即f[x-1]+1)、(f[X-2]+1)、(f[X-5]+1)中選擇一個最少的硬幣數。

3.3 組成部分三:初始條件和邊界情況

設置初始值,考慮邊界情況。

3.4 組成部分四:計算順序**

從上到下,從左到右。

四、LeetCode題完整解法


class Solution {
    public int coinChange(int[] coins, int amount) {
        
        int[] f = new int[amount+1];
        int coin_num = coins.length;
        //初始條件
        f[0] = 0;
        //f[x] = min{f[x-c1]+1,f[x-c2]+1,f[x-c3]+1}
        for(int x = 1;x<=amount;x++){
            f[x] = Integer.MAX_VALUE;
            for(int i = 0;i<coin_num;i++){
                // 考慮輸入[2],4,則需要保證f[x-coins[i]] != Integer.MAX_VALUE,即f[x-coins[i]]必須要是存在的狀態
                if(x >=coins[i] && f[x-coins[i]] != Integer.MAX_VALUE){
                    f[x] = Math.min(f[x-coins[i]]+1,f[x]);
                }
            }
        }
        // 考慮輸入[2],3,則amount = -1
        if(f[amount] == Integer.MAX_VALUE){
            return -1;
        }
        return f[amount];
    }
}

參考文檔

動態規划

參考視頻

動態規划入門 Introduction to Dynamic Programming
ACM專題講解:DP動態規划
算法數據結構面試通關(經驗全集)


免責聲明!

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



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