动态规划算法-01爬楼梯问题
爬樓梯問題
動態規劃
動態規劃算法將帶求解問題拆分成一系列相互交疊的子問題,通過遞推關系定義各子問題的求解策略,并隨時記錄子問題的解,最終獲得原始問題的解,避免了對交疊子問題的重復求解。
在動態規劃算法中有三要素,即最優子結構、邊界和狀態轉移函數。
- 最優子結構是指每個階段的最優狀態可以由之前某個階段的某個或某些狀態直接得到。
 - 邊界是指問題最小子集的解;(代碼中就是遞歸的出口或者dp數組的初始值)
 - 狀態轉移函數是指從一個階段向另一個階段過渡的具體模式,描述的是兩個相鄰子問題之間的關系。
 
凡是具有這三個要素的問題均可以使用動態規劃求解。
爬樓梯
本案例是一個簡單基礎的問題—爬樓梯問題,原題來自Leetcode題庫第70題,難度為Easy。
問題描述
假設小明要怕一個10級的樓梯,一步走一級或者一步走兩級,問他走到頂有多少種方法。舉個例子,他可以每次走一步,那么他走了10步;他也可以一次走兩步,他走了5步;解題的人要找到所有走的方案數。
示例1:
輸入: 2 輸出: 2 解釋: 有兩種方法可以爬到樓頂。 1. 1 階 + 1 階 2. 2 階示例2:
輸入: 3 輸出: 3 解釋: 有三種方法可以爬到樓頂。 1. 1 階 + 1 階 + 1 階 2. 1 階 + 2 階 3. 2 階 + 1 階問題分析
我們不妨直接看最后一步,他要么是從第九級再走一級到頂,要么是從第八級走兩步到頂。也就是說,最后那一步要么從第九級出發(這時只有唯一路徑,就是走一級);要么從第八級出發(這時只有唯一路徑,走兩級;如果走兩個一級,那么這種情況就是第一種的內容了)。換句話說,一旦知道從地面到第八級有X種走法,從地面到第九級有Y種走法,那么從地面到頂就是X+Y種走法。
為了方便描述,用F(n)F(n)F(n)表示第n級臺階的走法數量,可知F(10)=F(9)+f(8)F(10)=F(9)+f(8)F(10)=F(9)+f(8)。推廣一下,就是下式。
F(n)=F(n?1)+F(n?2)F(n)=F(n-1)+F(n-2)F(n)=F(n?1)+F(n?2)
這就是我們想要的狀態轉移函數和最優子結構。 當問題細化,只有一級臺階和兩級臺階時,不再細化,F(1)=1,F(2)=2F(1)=1,F(2)=2F(1)=1,F(2)=2,這其實就是邊界。
該問題動態規劃解法的三要素全都出現了,分別如下:
- 邊界:F(1)=1,F(2)=2F(1)=1,F(2)=2F(1)=1,F(2)=2
 - 最優子結構:F(n?1)和F(n?2)F(n-1)和F(n-2)F(n?1)和F(n?2)
 - 狀態轉移函數:F(n)=F(n?1)+F(n?2)F(n)=F(n-1)+F(n-2)F(n)=F(n?1)+F(n?2)
 
也就是說,這個問題的動態規劃建模完成了,為了時間和空間的效率(特別對于大問題),利用最優子結構的特性,我們一般采用自底向上的方式計算,也就是維護dp數組。也就是根據1、2算3,根據2、3算4,以此類推。
這個解法的基礎遞歸代碼如下,這是自頂而下的解法。
class Solution:def climbStairs(self, n: int) -> int:def dp(n):if n < 1:return 0if n == 1:return 1if n == 2:return 2if n > 2:return dp(n-1) + dp(n-2)return dp(n)優化思路
上述這種基礎的遞歸解法其實是存在大量的重疊子問題的,這是因為遞歸是對每個問題直接追溯到底層求解的,這中間存在大量的重復求解。舉個例子,當我們計算F(10)F(10)F(10)的時候需要去計算F(8)F(8)F(8)和F(9)F(9)F(9),但是,實際上我們在計算F(9)F(9)F(9)的時候也計算了F(8)F(8)F(8),這就是為什么上述遞歸解法平臺提交會出現TLE超時錯誤的原因。
優化的思路就是通過DP Table來存儲之前已求得的解,以自底而上的思路求解,,因此代碼可以優化如下。
class Solution:def climbStairs(self, n: int) -> int:dp = [0] * (n+1) if n >= 3 else [0] * 3dp[0], dp[1], dp[2] = 0, 1, 2for i in range(3, n+1):dp[i] = dp[i-1] + dp[i-2]return dp[n]進一步優化
上述的解法是可以在平臺上AC的,但是我們其實可以進一步優化,我們可以發現,其實為了求得最終解,我們始終使用的只有數組中的三個元素而已,因此不需要維護長度為n的數組空間,而是三個變量空間即可。這個思路叫做狀態壓縮,它可以大大節省空間開銷。
代碼如下。
class Solution:def climbStairs(self, n: int) -> int:dp = [0] * 3dp[0], dp[1], dp[2] = 1, 2, 0for i in range(2, n):dp[2] = dp[0] + dp[1]dp[0], dp[1] = dp[1], dp[2]return dp[2] if n > 2 else dp[n-1]補充說明
本文參考書為《你也能看得懂的Python算法書》,具體代碼可以查看我的Github,歡迎Star或者Fork。
總結
以上是生活随笔為你收集整理的动态规划算法-01爬楼梯问题的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Python工具包-中文处理工具Fool
 - 下一篇: 动态规划算法-02矿工挖矿问题