更多內容,歡迎關注微信公衆號:全菜工程師小輝。公衆號回覆關鍵詞,領取免費學習資料。程序員
動態規劃算法一直是面試手撕算法中比較有挑戰的一種類型。不少的分配問題或者調度問題實際上均可能用動態規劃進行解決。(固然,若是問題的規模較大,有時候會抽象模型使用動歸來解決,有時候則能夠經過不斷迭代的機率算法解決查找次優解)面試
因此,動歸很重要,至少算法思想很重要。算法
經過把原問題分解爲相對簡單的子問題的方式求解複雜問題的方法。動態規劃經常適用於有重疊子問題和最優子結構性質的問題。 > 最優子結構:當問題的最優解包含了其子問題的最優解時,稱該問題具備最優子結構性質。微信
> 重疊子問題:在用遞歸算法自頂向下解問題時,每次產生的子問題並不老是新問題,有些子問題被反覆計算屢次。動態規劃算法正是利用了這種子問題的重疊性質,對每個子問題只解一次,然後將其解保存在一個表格中,在之後儘量多地利用這些子問題的解。學習
不理解不用怕,結合後面題目來理解這些概念。這些概念徹底是已經會動歸的人來總結出來的,因此先理解動歸,而後再來看這些文縐縐的歸納。code
共同點:
兩者都要求原問題具備最優子結構性質,都是將原問題分而治之,分解成若干個規模較小(小到很容易解決)的子問題。而後將子問題的解合併,造成原問題的解。遞歸
不一樣點:ip
全部的動態規劃問題均可以經過多層嵌套循環遍歷全部的可能,將符合條件的個數統計起來。只是時間複雜度是指數級的,因此不推薦。leetcode
> 上面提到的三個問題是動態規劃裏很常見的題目,題目內容能夠百度查看一下。篇幅緣由,本文後邊只講解前兩道題get
例1: Climbing Stairs(爬樓梯問題)
leetcode原題:你正在爬一個有n個臺階的樓梯,每次只能上1個或者2個臺階,那麼到達頂端共有多少種不一樣的方法?
class Solution { int climbStairs(int n) { if (n <= 2) { return n; } else { return climbStairs(n - 1) + climbStairs(n - 2); } } }
> 遞歸的時間複雜度是由遞歸層數和最優子結構的個數決定的。這裏的階梯數是 N ,最優子結構個數是2。若是想象成一個二叉樹,那麼就能夠認爲是一個高度爲N-1,節點個數接近2的N-1次方的樹,所以此方法的時間複雜度能夠近似的看做是O(2<sup>N</sup>) 。
class Solution { private Map<integer, integer> map = new HashMap<>(); int climbStairs(int n) { if (n <= 2) { return n; } else if (map.containsKey(n)) { return map.get(n); } else { int value = climbStairs(n - 1) + climbStairs(n - 2); map.put(n, value); return value; } } }
class Solution { int climbStairs(int n) { if (n <= 2) { return n; } // 邊界條件 int a = 1; int b = 2; int result = 0; // 最優子結構與最終問題之間的關係 for (int i = 3; i <= n; i++) { result = a + b; a = b; b = result; } return result; } }
> 空間複雜度O(1), 時間複雜度O(N)
例2: Making change using the fewest coins(最少找零錢問題)
Google面試題:假設你是一家自動售貨機制造商的程序員。你的公司正設法在每一筆交易 找零時都能提供最少數目的硬幣以便工做能更加簡單。已知硬幣有四種(1美分,5美分,10美分,25美分)。假設一個顧客投了1美圓來購買37美分的物品 ,你用來找零的硬幣的最小數量是多少?
class Solution { Set<integer> coinSet = new HashSet<integer>() { { add(1); add(5); add(10); add(25); } }; int getFewestCoins(int n) { if (n < 1) { return 0; } if (coinSet.contains(n)) { return 1; } int minCoins = n; int numCoins = Integer.MAX_VALUE; for (int coin : coinSet) { if (n >= coin) { // 若是要計算的n小於單個硬幣金額,則不能出如今狀態轉移方程中 numCoins = 1 + getFewestCoins(n - coin); } // 更新最小值 if (numCoins < minCoins) { minCoins = numCoins; } } return minCoins; } }
備忘錄算法:
就是將遞歸裏計算的中間變量都保存在一個哈希表,代碼略。
動態規劃:
自底向上,從找零數等於1開始往上迭代,參考最優子結構,記錄下來最少硬幣數。一直迭代到實際要求。
class Solution { Set<integer> coinSet = new HashSet<integer>() { { add(1); add(5); add(10); add(25); } }; int getFewestCoins(int n) { int[] list = new int[n + 1]; List<integer> subCal = new ArrayList<>(); for (int i = 0; i <= n; i++) { // 邊界 if (i <= 1) { list[i] = i; continue; } for (int cent : coinSet) { if (i >= cent) { subCal.add(list[i - cent] + 1); } } list[i] = Collections.min(subCal); subCal.clear(); } return list[n]; } }
更多內容,歡迎關注微信公衆號:全菜工程師小輝。公衆號回覆關鍵詞,領取免費學習資料。
</integer></integer></integer></integer></integer></integer,>