LeetCode刷題之動態規劃思想

動態規劃

動態規劃思想:將原問題拆解成若干子問題,同時保存子問題的答案,使得每個子問題只求解一次,最終獲得原問題的答案。

求解思路

在這裏插入圖片描述

  • 大多數動態規劃問題都是一個遞歸問題;
  • 在遞歸的過程中會發現很多重疊子問題(出現重複計算子問題的情況);
  • 可以使用記憶化搜索的方式來解決問題;
  • 通常解決動態規劃問題時,先自頂向下的思考問題,最後再通過自底向上的動態規劃解決問題。

動態規劃中都是通過這種思路來解決問題的。

下面以求解斐波那契數列來說明上面的過程。

斐波那契數列

F(0)=1,F(1)=1,F(n)=F(n1)+F(n2)F(0)=1,F(1)=1,F(n) = F(n-1) +F(n-2)

遞歸問題

這個式子可以很容易的寫成遞歸形式。

def fib(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    return fib(n-1) + fib(n-2)

遞歸形式的思路雖然簡單,但是這段代碼的耗時是指數增長的。
在這裏插入圖片描述
在jupyter上求fib(30)就耗時256毫秒。

重疊子問題

下面我們來看下爲什麼計算這麼慢。

在這裏插入圖片描述

如果我們想要計算fib(5),那麼根據定義,我們要計算fib(4)fib(3)

在這裏插入圖片描述
如果要計算4,那麼就要計算3和2。以此類推,我們可以畫出整個計算斐波那契數列的遞歸樹。

在這個遞歸樹種,每個葉子節點都到了1或0的終止條件。這裏每個節點都是一次計算。

從上圖可以發現,我們進行了多次的重複計算。

在這裏插入圖片描述
拿求解fib(2)來說,我們計算了3次。

記憶化搜索-自頂向下的解決問題

對於這些重複計算,是否可以只計算一次呢。很簡單的思路是用一個數據結構將之前的計算結果保存起來。

memo = {}  #保存計算結果

def fib(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    if n not in memo: # 如果沒有計算過再去計算
        memo[n] =  fib(n-1) + fib(n-2)
    return memo[n]

這種方式就是記憶化搜索。我們使用了遞歸搜索的方式,但是使用了memo進行記憶。
在這裏插入圖片描述
現在計算起來就非常快了。

在這裏插入圖片描述
fib(1000)也可以計算了。

記憶化搜索的實質是在遞歸的基礎上加上記憶化的過程。
遞歸是一種自頂向下的解決問題。也就是說,我們沒有從最基本的問題開始解決,而是假設最基本的問題已經解決了。我們已經會求fib(n-1)fib(n-2)了,那麼求fib(n)就是把它們加起來就好了。

通常如果我們能自頂向下的解決問題的話,我們也能自底向上的解決問題。

動態規劃-自底向上的解決問題

memo = {} 

def fib(n):
    memo = {0:0,1:1} #memo中存的就是第i個斐波那契數列
    for i in range(2,n+1):
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]

這樣的一個過程就是自底向上的解決問題,我們先解決小數據量上的結果(i是從2開始的),然後層層遞推來解決更大的數據量的問題。

這樣的過程就是動態規劃。

通常解決動態規劃問題時,先自頂向下的思考問題,最後再通過自底向上的動態規劃解決問題。

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