動態規劃(dynamic programming)是運籌學的乙個分支,是求解決策過程(decision process)最優化的數學方法。
動態規劃演算法通常基於乙個遞推公式及乙個或多個初始狀態。 當前子問題的解將由上一次子問題的解推出。
要解決乙個給定的問題,我們需要解決其不同部分(即解決子問題),再合併子問題的解以得出原問題的解。
通常許多子問題非常相似,為此動態規劃法試圖只解決每個子問題一次,從而減少計算量。
一旦某個給定子問題的解已經算出,則將其記憶化儲存,以便下次需要同乙個子問題解之時直接查表。
這種做法在重複子問題的數目關於輸入的規模呈指數增長時特別有用。
動態規劃有三個核心元素:
1.最優子結構
2.邊界
3.狀態轉移方程
我們來看一到題目
有一座高度是10級台階的樓梯,從下往上走,每跨一步只能向上1級或者2級台階。求出一共有多少種走法。比如,每次走1級台階,一共走10步,這是其中一種走法。
再比如,每次走2級台階,一共走5步,這是另一種走法。
但是這樣乙個個算太麻煩了,我們可以只去思考最後一步怎麼走,如下圖
這樣走到第十個樓梯的走法 = 走到第八個樓梯 + 走到第九個樓梯
我們用f(n)來表示 走到第n個樓梯的走法,所以就有了f(10) = f(9) + f(8)
然後f(9) = f(8) + f(7), f(8) = f(7) + f(6)......
這樣我們就得出來乙個遞迴式:
f(n) = f(n-1) + f(n-2);
還有兩個初始狀態:
f(1) = 1;
f(2) = 2;
這樣就得出了第一種解法
這種方法的時間複雜度為o(2^n)function getways(n)
可以看到這是一顆二叉樹,數的節點個數就是我們遞迴方程需要計算的次數,
數的高度為n,節點個數近似於2^n
所以時間複雜度近似於o(2^n)
但是這種方法能不能優化呢?
我們會發現有些值被重複計算,如下圖
相同顏色代表著重複的部分,那麼我們可不可以把這些重複計算的值記錄下來呢?
這樣的優化就有了第二種方法
因為map裡最終會存放n-2個鍵值對,所以空間複雜度為o(n),時間複雜度也為o(n)繼續想一想這就是最優的解決方案了嗎?const map = new map();
function getways(n)
const value = getways(n-1) + getways(n-2);
map.set(n, value);
return value;
}
我們回到一開始的思路,我們是假定前面的樓梯已經走完,只考慮最後一步,所以才得出來f(n) = f(n-1) + f(n-2)的遞迴式,這是乙個置頂向下求解的式子
一般來說,按照正常的思路應該是一步一步往上走,應該是自底向上去求解才比較符合正常人的思維,我們來看看行不行的通
這是一開始走的乙個和兩個樓梯的走法數,即之前說的初始狀態
這是進行了一次迭代得出了3個樓梯的走法,f(3)只依賴於f(1) 和 f(2)
繼續看下一步
這裡又進行了一次迭代得出了4個樓梯的走法,f(4)只依賴於f(2) 和 f(3)
我們發現每次迭代只需要前兩次迭代的資料,不用像備忘錄一樣去儲存所有子狀態的資料
這是我們可以再看看當前的時間複雜度和空間複雜度function getways(n)
return temp;
}
當前時間複雜度仍為o(n),但空間複雜度降為o(1)
這就是理想的結果
這只是動態規劃裡最簡單的題目之一,因為它只有乙個變化維度
當變化維度變成兩個、三個甚至更多時,會更加複雜,揹包問題就是比較典型的多維度問題,有興趣的可以去網上看看《揹包九講》
動態規劃 爬樓梯
假設你正在爬樓梯,需要n步你才能到達頂部。但每次你只能爬一步或者兩步,你能有多少種不同的方法爬到樓頂部?比如n 3,1 1 1 1 2 2 1 3,共有3種不同的方法 返回 3 解題思路 沒接觸過動態規劃的時候,我用排列組合做的,在我這篇部落格中 可以看一下。實際上,這個題目就是乙個斐波那契數列,這...
爬樓梯 動態規劃
假設你正在爬樓梯。需要 n 階你才能到達樓頂。每次你可以爬 1 或 2 個台階。你有多少種不同的方法可以爬到樓頂呢?注意 給定 n 是乙個正整數。示例 1 輸入 2 輸出 2 解釋 有兩種方法可以爬到樓頂。1.1 階 1 階 2.2 階 示例 2 輸入 3 輸出 3 解釋 有三種方法可以爬到樓頂。1...
動態規劃 爬樓梯
假設你正在爬樓梯。需要 n 階你才能到達樓頂。每次你可以爬 1 或 2 個台階。你有多少種不同的方法可以爬到樓頂呢?注意 給定 n 是乙個正整數。示例 1 輸入 2 輸出 2 解釋 有兩種方法可以爬到樓頂。1.1 階 1 階 2.2 階 示例 2 輸入 3 輸出 3 解釋 有三種方法可以爬到樓頂。1...