這個問題和斐波納契數字問題是同樣的。題目描述以下:python
一隻青蛙一次能夠跳上1級臺階,也能夠跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(前後次序不一樣算不一樣的結果)。算法
首先用遞歸的思路來考慮。遞歸是倒着分析的。緩存
假如只差一步就能走完整個臺階,要分爲幾種狀況?由於每一步能走一級或者兩級臺階,因此有以下兩種狀況:函數
因此第n步的走法由兩種方法的和構成。若是定義F(n)爲第n步的走法,那麼F(n)=F(n-1)+F(n-2)。這個公式稱之爲遞推公式。 當只有1級臺階和2級臺階時走法很明顯,即F(1)=一、F(2)=2。稱之爲邊界條件。 兩部分具有以後,很容易能夠寫出代碼:優化
def fun(n):
if n==2:
return 2
if n==1:
return 1
return fun(n-1)+fun(n-2)
複製代碼
驗證的時候會發現一個問題,若是n比較大,那麼會致使計算機棧的溢出,即使是n=100的時候也須要至關長的計算時間。那麼問題出在哪裏呢?spa
咱們不妨把上面的遞歸式子以樹的形式表示出來,結果以下:code
F(5)
/ \
F(4) F(3)
/ \ / \
F(3) F(2) F(2) F(1)
/ \ / \ / \
F(2) F(1) F(1) F(0) F(1) F(0)
/ \
F(1) F(0)
複製代碼
能夠看到這其中有不少重複求解部分,稱之爲重疊子問題。 一種想到的改進方法是咱們可不能夠把遞歸計算中某些計算過的結果存起來,來避免這個問題。下面介紹記憶化搜索和LRU 緩存策略實現這種改進方法。cdn
記憶化搜索的思路以下:每當咱們須要解決子問題時,咱們首先查找查找表。若是預先計算的值存在,那麼咱們返回該值,不然咱們計算值,並將結果放在查找表中,以便之後能夠重複使用。blog
lookup = [0,1,2]+[0]*100
def fun(n):
if lookup[n] == 0:
lookup[n] = fun(n-1)+fun(n-2)
return lookup[n]
複製代碼
LRU(Least recently used,最近最少使用),其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。對於本題目,若是是高頻出現的計算函數的結果會被放到緩存中,再次出現只須要在緩存中讀取便可,和記憶化搜索相似。python有LRU的內置函數:排序
from functools import lru_cache
@lru_cache(None)
def fun(n):
if n==2:
return 2
if n==1:
return 1
return fun(n-1)+fun(n-2)
複製代碼
有了上述方法,n=100已經能夠計算了。可是雖然有這兩個緩解的方法,可是仍存在問題。當n=1000的時候仍然會存在堆棧溢出的問題。
上面的思考都是基於遞歸的,即自頂而下的計算方法。若是咱們換個思路,自底而上呢?
其實和上面的記憶化搜索很像了。首選記錄n=1的狀況和n=2的狀況,而後依次向上計算,每次計算都存表便可。
N = 10
dp = [0]*(N+1)
dp[1] = 1
dp[2] = 2
def fun(n):
for i in range(n+1):
if i>2:
dp[i] = dp[i-1] + dp[i-2]
return dp[-1]
複製代碼
這種方法存的表稱之爲DP Table,這種解決思路稱之爲動態規劃。本題目的DP Table是一維的,因此稱之爲一維動態規劃。
固然針對這個問題,對空間還能夠進一步優化:
def fun(n):
if n == 1:
return 1
if n == 2:
return 2
pre,prepre = 2,1
for i in range(n - 2):
prepre, pre = pre, pre + prepre
return pre
複製代碼
上面咱們已經見到過了:遞歸採用的是「由上而下」的解題策略並帶有可能的」子問題「重複調用,時間複雜度天然高,並且容易形成堆棧的溢出。
例如求解Fib數列就包含重複子問題,此時考慮動態規劃
可是求解歸併排序的時候每一個子問題都是獨立的,所以考慮分治遞歸
二者的區別在於:動態規劃的下一個子階段的求解是創建在上一個子階段的解的基礎上,進行進一步的求解。
貪心算法每走一步都是不可撤回的,而動態規劃是在一個問題的多種策略中尋找最優策略,因此動態規劃中前一種策略可能會被後一種策略推翻。