1. 程式人生 > >看動畫輕鬆理解“遞迴”與“動態規劃”

看動畫輕鬆理解“遞迴”與“動態規劃”

640?wx_fmt=jpeg


作者 | 程式設計師小吳

來源 | 五分鐘學演算法

在學習「資料結構和演算法」的過程中,因為人習慣了平鋪直敘的思維方式,所以「遞迴」與「動態規劃」這種帶迴圈概念(繞來繞去)的往往是相對比較難以理解的兩個抽象知識點。

程式設計師小吳打算使用動畫的形式來幫助理解「遞迴」,然後通過「遞迴」的概念延伸至理解「動態規劃」演算法思想。

什麼是遞迴

先下定義:遞迴演算法是一種直接或者間接呼叫自身函式或者方法的演算法。

通俗來說,遞迴演算法的實質是把問題分解成規模縮小的同類問題的子問題,然後遞迴呼叫方法來表示問題的解。它有如下特點:

  1. 一個問題的解可以分解為幾個子問題的解

  2. 這個問題與分解之後的子問題,除了資料規模不同,求解思路完全一樣

  3. 存在遞迴終止條件,即必須有一個明確的遞迴結束條件,稱之為遞迴出口

640?wx_fmt=gif

遞迴動畫

通過動畫一個一個特點來進行分析。

1.一個問題的解可以分解為幾個子問題的解

子問題就是相對與其前面的問題資料規模更小的問題。

在動圖中①號問題(一塊大區域)劃分為②號問題,②號問題由兩個子問題(兩塊中區域)組成。

2. 這個問題與分解之後的子問題,除了資料規模不同,求解思路完全一樣

「①號劃分為②號」與「②號劃分為③號」的邏輯是一致的,求解思路是一樣的。

3. 存在遞迴終止條件,即存在遞迴出口

把問題分解為子問題,把子問題再分解為子子問題,一層一層分解下去,不能存在無限迴圈,這就需要有終止條件。

①號劃分為②號,②號劃分為③號,③號劃分為④號,劃分到④號的時候每個區域只有一個不能劃分的問題,這就表明存在遞迴終止條件。

從遞迴的經典示例開始

一、陣列求和

640?wx_fmt=gif

陣列求和


  

11Sum(arr[0...n-1]) = arr[0] + Sum(arr[1...n-1])

後面的 Sum 函式要解決的就是比前一個 Sum 更小的同一問題。


  

11Sum(arr[1...n-1]) = arr[1] + Sum(arr[2...n-1])

以此類推,直到對一個空陣列求和,空陣列和為 0 ,此時變成了最基本的問題。


  

11Sum(arr[n-1...n-1] ) = arr[n-1] + Sum([])

二、漢諾塔問題

漢諾塔(Hanoi Tower)問題也是一個經典的遞迴問題,該問題描述如下:

漢諾塔問題:古代有一個梵塔,塔內有三個座A、B、C,A座上有64個盤子,盤子大小不等,大的在下,小的在上。有一個和尚想把這個盤子從A座移到B座,但每次只能允許移動一個盤子,並且在移動過程中,3個座上的盤子始終保持大盤在下,小盤在上。

640?wx_fmt=gif

兩個盤子

640?wx_fmt=gif三個盤子

① 如果只有 1 個盤子,則不需要利用 B 塔,直接將盤子從 A 移動到 C 。

② 如果有 2 個盤子,可以先將盤子 2 上的盤子 1 移動到 B ;將盤子 2 移動到 C ;將盤子 1 移動到 C 。這說明了:可以藉助 B 將 2 個盤子從 A 移動到 C ,當然,也可以藉助 C 將 2 個盤子從 A 移動到 B 。

③ 如果有 3 個盤子,那麼根據 2 個盤子的結論,可以藉助 C 將盤子 3 上的兩個盤子從 A 移動到 B ;將盤子 3 從 A 移動到 C ,A 變成空座;藉助 A 座,將 B 上的兩個盤子移動到 C 。

④ 以此類推,上述的思路可以一直擴充套件到 n 個盤子的情況,將將較小的 n-1個盤子看做一個整體,也就是我們要求的子問題,以藉助 B 塔為例,可以藉助空塔 B 將盤子A上面的 n-1 個盤子從 A 移動到 B ;將A 最大的盤子移動到 C , A 變成空塔;藉助空塔 A ,將 B 塔上的 n-2 個盤子移動到 A,將 C 最大的盤子移動到 C, B 變成空塔……

三、爬臺階問題

問題描述:

一個人爬樓梯,每次只能爬1個或2個臺階,假設有n個臺階,那麼這個人有多少種不同的爬樓梯方法?

先從簡單的開始,以 4 個臺階為例,可以通過每次爬 1 個臺階爬完樓梯:

640?wx_fmt=gif每次爬 1 個臺階

可以通過先爬 2 個臺階,剩下的每次爬 1 個臺階爬完樓梯

640?wx_fmt=gif

先爬 2 個臺階

在這裡,可以思考一下:可以根據第一步的走法把所有走法分為兩類:

① 第一類是第一步走了 1 個臺階
② 第二類是第一步走了 2 個臺階

所以 n 個臺階的走法就等於先走 1 階後,n-1 個臺階的走法 ,然後加上先走 2 階後,n-2 個臺階的走法。

用公式表示就是:

f(n) = f(n-1)+f(n-2)

有了遞推公式,遞迴程式碼基本上就完成了一半。那麼接下來考慮遞迴終止條件。

當有一個臺階時,我們不需要再繼續遞迴,就只有一種走法。

所以 f(1)=1

通過用 n = 2n = 3 這樣比較小的數試驗一下後發現這個遞迴終止條件還不足夠。

n = 2 時,f(2) = f(1) + f(0)。如果遞迴終止條件只有一個f(1) = 1,那 f(2)就無法求解,遞迴無法結束。  
所以除了
f(1) = 1這一個遞迴終止條件外,還要有f(0) = 1,表示走 0 個臺階有一種走法,從思維上以及動圖上來看,這顯得的有點不符合邏輯。所以為了便於理解,把 f(2) = 2 作為一種終止條件,表示走 2 個臺階,有兩種走法,一步走完或者分兩步來走。

總結如下:

① 假設只有一個臺階,那麼只有一種走法,那就是爬 1 個臺階
② 假設有兩個個臺階,那麼有兩種走法,一步走完或者分兩步來走

640?wx_fmt=gif

遞迴終止條件

通過遞迴條件:


  

11f(1) = 1;
22f(2) = 2;
33f(n) = f(n-1)+f(n-2)

很容易推匯出遞迴程式碼:


  

11int f(int n) {
22  if (n == 1return 1;
33  if (n == 2return 2;
44  return f(n-1) + f(n-2);
55}

通過上述三個示例,總結一下如何寫遞迴程式碼:

  1. 找到如何將大問題分解為小問題的規律

  2. 通過規律寫出遞推公式

  3. 通過遞迴公式的臨界點推敲出終止條件、

  4. 將遞推公式和終止條件翻譯成程式碼

什麼是動態規劃

介紹動態規劃之前先介紹一下分治策略(Divide and Conquer)。

分治策略

將原問題分解為若干個規模較小但類似於原問題的子問題(Divide),「遞迴」的求解這些子問題(Conquer),然後再合併這些子問題的解來建立原問題的解。

因為在求解大問題時,需要遞迴的求小問題,因此一般用「遞迴」的方法實現,即自頂向下。

動態規劃(Dynamic Programming)

動態規劃其實和分治策略是類似的,也是將一個原問題分解為若干個規模較小的子問題,遞迴的求解這些子問題,然後合併子問題的解得到原問題的解。

區別在於這些子問題會有重疊,一個子問題在求解後,可能會再次求解,於是我們想到將這些子問題的解儲存起來,當下次再次求解這個子問題時,直接拿過來就是。

其實就是說,動態規劃所解決的問題是分治策略所解決問題的一個子集,只是這個子集更適合用動態規劃來解決從而得到更小的執行時間。

即用動態規劃能解決的問題分治策略肯定能解決,只是執行時間長了。因此,分治策略一般用來解決子問題相互對立的問題,稱為標準分治,而動態規劃用來解決子問題重疊的問題。

與「分治策略」「動態規劃」概念接近的還有「貪心演算法」「回溯演算法」,由於篇幅限制,程式設計師小吳就不在這進行展開,在後續的文章中將分別詳細的介紹「貪心演算法」、「回溯演算法」、「分治演算法」,敬請關注:)

將「動態規劃」的概念關鍵點抽離出來描述就是這樣的:

1.動態規劃法試圖只解決每個子問題一次
2.一旦某個給定子問題的解已經算出,則將其記憶化儲存,以便下次需要同一個子問題解之時直接查表。

從遞迴到動態規劃

還是以 爬臺階 為例,如果以遞迴的方式解決的話,那麼這種方法的時間複雜度為O(2^n),具體的計算可以檢視筆者之前的文章 《冰與火之歌:時間複雜度與空間複雜度》。

相同顏色代表著 爬臺階問題 在遞迴計算過程中重複計算的部分。

640?wx_fmt=png爬臺階的時間複雜度

通過圖片可以發現一個現象,我們是 自頂向下 的進行遞迴運算,比如:f(n)f(n-1)與f(n-2)相加,f(n-1)f(n-2)f(n-3)相加。

思考一下:如果反過來,採取自底向上,用迭代的方式進行推導會怎麼樣了?

下面通過表格來解釋 f(n)自底向上的求解過程。

臺階數    1   2   3   4   5   6   7   8   9
走法數    1   2

表格的第一行代表了樓梯臺階的數目,第二行代表了若干臺階對應的走法數。
其中
f(1) = 1f(2) = 2是前面明確的結果。

第一次迭代,如果臺階數為 3 ,那麼走法數為 3 ,通過 f(3) = f(2) + f(1)得來。

640?wx_fmt=png

第二次迭代,如果臺階數為 4 ,那麼走法數為 5 ,通過 f(4) = f(3) + f(2)得來。

640?wx_fmt=png

640?wx_fmt=gif


由此可見,每一次迭代過程中,只需要保留之前的兩個狀態,就可以推到出新的狀態。

show me the code


  

11int f(int n) {
2 2    if (n == 1return 1;
3 3    if (n == 2return 2;
4 4    // a 儲存倒數第二個子狀態資料,b 儲存倒數第一個子狀態資料, temp 儲存當前狀態的資料
5 5    int a = 1, b = 2;
6 6    int temp = a + b;
7 7    for (int i = 3; i <= n; i++) {
8 8        temp = a + b;
9 9        a = b;
1010        b = temp; 
1111    }
1212    return temp; 
1313}

程式從 i = 3 開始迭代,一直到 i = n 結束。每一次迭代,都會計算出多一級臺階的走法數量。迭代過程中只需保留兩個臨時變數 a 和 b ,分別代表了上一次和上上次迭代的結果。為了便於理解,引入了temp 變數。temp 代表了當前迭代的結果值。

看一看出,事實上並沒有增加太多的程式碼,只是簡單的進行了優化,時間複雜度便就降為O(n),而空間複雜度也變為O(1),這,就是「動態規劃」的強大!

詳解動態規劃

「動態規劃」中包含三個重要的概念:

【最優子結構】
【邊界】
【狀態轉移公式】

在「 爬臺階問題 」中

f(10) = f(9) + f(8) 是【最優子結構】  
f(1) 與 f(2) 是【邊界】  
f(n) = f(n-1) + f(n-2)【狀態轉移公式】

「 爬臺階問題 」 只是動態規劃中相對簡單的問題,因為它只有一個變化維度,如果涉及多個維度的話,那麼問題就變得複雜多了。

難點就在於找出 「動態規劃」中的這三個概念。

比如「 國王和金礦問題 」。

國王和金礦問題

有一個國家發現了 5 座金礦,每座金礦的黃金儲量不同,需要參與挖掘的工人數也不同。參與挖礦工人的總數是 10 人。每座金礦要麼全挖,要麼不挖,不能派出一半人挖取一半金礦。要求用程式求解出,要想得到儘可能多的黃金,應該選擇挖取哪幾座金礦?

640?wx_fmt=png

5 座金礦


找出 「動態規劃」中的這三個概念

國王和金礦問題中的【最優子結構】

640?wx_fmt=png

國王和金礦問題中的【最優子結構】

國王和金礦問題中的【最優子結構】有兩個:

① 4 金礦 10 工人的最優選擇
② 4 金礦 (10 - 5) 工人的最優選擇

4 金礦的最優選擇與 5 金礦的最優選擇之間的關係是

MAX[(4 金礦 10 工人的挖金數量),(4 金礦 5 工人的挖金數量 + 第 5 座金礦的挖金數量)]

國王和金礦問題中的【邊界】

國王和金礦問題中的【邊界】 有兩個:

① 當只有 1 座金礦時,只能挖這座唯一的金礦,得到的黃金數量為該金礦的數量
② 當給定的工人數量不夠挖 1 座金礦時,獲取的黃金數量為 0

國王和金礦問題中的【狀態轉移公式】

我們把金礦數量設為 N,工人數設為 W,金礦的黃金量設為陣列G[],金礦的用工量設為陣列P[],得到【狀態轉移公式】:

邊界值:F(n,w) = 0    (n <= 1, w < p[0])

F(n,w) = g[0]   (n==1, w >= p[0])

F(n,w) = F(n-1,w)    (n > 1, w < p[n-1])

F(n,w) = max(F(n-1,w),  F(n-1,w-p[n-1]) + g[n-1])    (n &gt; 1, w &gt;= p[n-1])

國王和金礦問題中的【實現】

先通過幾幅動畫來理解 「工人」 與 「金礦」 搭配的方式

1.只挖第一座金礦

640?wx_fmt=gif

只挖第一座金礦

在只挖第一座金礦前面兩個工人挖礦收益為 零,當有三個工人時,才開始產生收益為 200,而後即使增加再多的工人收益不變,因為只有一座金礦可挖。

2.挖第一座與第二座金礦
640?wx_fmt=gif挖第一座金礦與第二座金礦

在第一座與第二座金礦這種情況中,前面兩個工人挖礦收益為 零,因為 W < 3,所以F(N,W) = F(N-1,W) = 0。

當有 三 個工人時,將其安排挖第 一 個金礦,開始產生收益為 200。

當有 四 個工人時,挖礦位置變化,將其安排挖第 二 個金礦,開始產生收益為 300。

當有 五、六 個工人時,由於多於 四 個工人的人數不足以去開挖第 一 座礦,因此收益還是為 300。

當有 七 個工人時,可以同時開採第 一 個和第 二 個金礦,開始產生收益為 500。

3.挖前三座金礦

這是「國王和金礦」 問題中最重要的一個動畫之一,可以多看幾遍

640?wx_fmt=gif

挖前三座金礦

4.挖前四座金礦

這是「國王和金礦」 問題中最重要的一個動畫之一,可以多看幾遍

640?wx_fmt=gif

挖前四座金礦

國王和金礦問題中的【規律】

仔細觀察上面的幾組動畫可以發現:

對比「挖第一座與第二座金礦」和「挖前三座金礦」,在「挖前三座金礦」中,3 金礦 7 工人的挖礦收益,來自於 2 金礦 7 工人和  2 金礦 4 工人的結果,Max(500,300 + 350) = 650;

對比「挖前三座金礦」和「挖前四座金礦」,在「挖前四座金礦」中,4 金礦 10 工人的挖礦收益,來自於 3 金礦 10 工人和  3 金礦 5 工人的結果,Max(850,400 + 300) = 850;

國王和金礦問題中的【動態規劃程式碼】


  

1 1程式碼來源:https://www.cnblogs.com/SDJL/archive/2008/08/22/1274312.html
2 2
3 3//maxGold[i][j] 儲存了i個人挖前j個金礦能夠得到的最大金子數,等於 -1 時表示未知
4 4int maxGold[max_people][max_n];
5 5
6 6int GetMaxGold(int people, int mineNum){
7 7    int retMaxGold;                            //宣告返回的最大金礦數量
8 8    //如果這個問題曾經計算過
9 9    if(maxGold[people][mineNum] != -1){
1010        retMaxGold = maxGold[people][mineNum]; //獲得儲存起來的值
1111    }else if(mineNum == 0) {                   //如果僅有一個金礦時 [ 對應動態規劃中的"邊界"]
1212        if(people >= peopleNeed[mineNum])      //當給出的人數足夠開採這座金礦
1313            retMaxGold = gold[mineNum];        //得到的最大值就是這座金礦的金子數
1414        else                                   //否則這唯一的一座金礦也不能開採
1515            retMaxGold = 0;                    //得到的最大值為 0 個金子
1616    }else if(people >= peopleNeed[mineNum])    // 如果人夠開採這座金礦[對應動態規劃中的"最優子結構"]
1717    {
1818        //考慮開採與不開採兩種情況,取最大值
1919        retMaxGold = max(
2020                         GetMaxGold(people - peopleNeed[mineNum],mineNum - 1) + gold[mineNum],
2121                         GetMaxGold(people,mineNum - 1)
2222                         );
2323    }else//否則給出的人不夠開採這座金礦 [ 對應動態規劃中的"最優子結構"]
2424    {
2525        retMaxGold = GetMaxGold(people,mineNum - 1);     //僅考慮不開採的情況
2626        maxGold[people][mineNum] = retMaxGold;
2727    }
2828    return retMaxGold;
2929}

640?wx_fmt=png動態規劃程式碼

希望通過這篇文章,大家能對「遞迴」與「動態規劃」有一定的理解。後續將以「動態規劃」為基礎研究多重揹包演算法、迪杰特斯拉演算法等更高深的演算法問題,同時「遞迴」的更多概念也會在「分治演算法」章節再次延伸,敬請對程式設計師小吳保持關注。

相關推薦

動畫輕鬆理解動態規劃

作者 | 程式設計師小吳 來源 | 五分鐘學演算法 在學習「資料結構和演算法」的過程中,因為人習慣了平鋪直敘的思維方式,所以「遞迴」與「動態規劃」這種帶迴圈概念(繞來繞去)的往往是相對比較難以理解的兩個抽象知識點。 程式設計師小吳打算使用動畫的形式來幫助理解「遞迴」,

動態規劃---最長遞增子序列問題

【問題】 給定陣列arr,返回arr的最長遞增子序列 【基本思路】 首先介紹時間複雜度為O(N^2)的方法。具體過程如下: 生成長度為N(arr的長度)的陣列dp,dp[i]表示在以arr[i

動態規劃

矩陣的最小路徑和 給一個矩陣,從左上角走到右下角,只能向右或向下走,求和最小的路徑。 #include <bits/stdc++.h> using namespace std; // 普通的動態規劃,空間複雜度和時間複雜度均為O(N*M)

跟左神學演算法10 經典演算法 - 動態規劃

內容: 1、遞迴與多型規劃的關係 2、暴力遞迴 3、動態規劃       1、遞迴與多型規劃的關係 暴力遞迴: 把問題轉化為規模縮小了的同類問題的子問題 有明確的不需要繼續進行遞迴的條件(base case) 有當得到了子問題的結果之後的決策過程

動態規劃---換錢的方法數

【問題】   給定陣列arr,arr中所有的值都為整數且不重複。每個值代表一種面值的貨幣,每種貨幣有無數張,再給定一個整數aim代表要找的錢數,求換錢的方法有多少種。 【基本思路】 這道題的經典之處在於它可以體現暴力遞迴、記憶搜尋、動態規劃之間的關係,並可

動態規劃---斐波那契系列問題的動態規劃矩陣乘法

【題目】 給定整數N,返回斐波那契數列的第N項 假設農場中成熟的母牛每年只會生一頭小母牛,並且永遠不會死。第一年農場有1只成熟的牛,從第二年開始,母牛開始生小母牛。每隻小母牛3年之後 成熟又可以生小母牛。給定整數N,返回N年後牛的數量。 【基本

斐波那契數列(一)--對比動態規劃(JAVA)

兔子繁殖問題: 這是一個有趣的古典數學問題,著名義大利數學家Fibonacci曾提出一個問題:有一對小兔子,從出生後第3個月起每個月都生一對兔子。小兔子長到第3個月後每個月又生一對兔子。按此規律,假設沒有兔子死亡,第一個月有一對剛出生的小兔子,問第n個月有多少

動畫輕鬆理解動態規劃

在學習「資料結構和演算法」的過程中,因為人習慣了平鋪直敘的思維方式,所以「遞迴」與「動態規劃」這種帶迴圈概念(繞來繞去)的往往是相對比較難以理解的兩個抽象知識點。 程式設計師小吳打算使用動畫的形式來幫助理解「遞迴」,然後通過「遞迴」的概念延伸至理解「動態規劃」演算法思想。 什麼是遞迴 先下定義:遞迴演算

動畫輕鬆理解動態規劃」(完整版)

Follow: MisterBooo · GitHub 如果文章程式碼不便閱讀,可點選這裡檢視原文:)   在學習「資料結構和演算法」的過程中,因為人習慣了平鋪直敘的思維方式,所以「遞迴」與「動態規劃」這種帶迴圈概念(繞來繞去)的往往是相對比較難以理解的兩個抽象知識點。 程式設計師

輕鬆理解資料結構算法系列(Radix樹)

前言 推出一個新系列,《看圖輕鬆理解資料結構和演算法》,主要使用圖片來描述常見的資料結構和演算法,輕鬆閱讀並理解掌握。本系列包括各種堆、各種佇列、各種列表、各種樹、各種圖、各種排序等等幾十篇的樣子。 Radix樹 Radix樹,即基數樹,也稱壓縮字首樹,是一種提供key-value儲存查詢的資料結構。與

輕鬆理解資料結構算法系列(希爾排序)

前言 推出一個新系列,《看圖輕鬆理解資料結構和演算法》,主要使用圖片來描述常見的資料結構和演算法,輕鬆閱讀並理解掌握。本系列包括各種堆、各種佇列、各種列表、各種樹、各種圖、各種排序等等幾十篇的樣子。 希爾排序 希爾排序是希爾(Donald Shell)提出的一種

動畫輕鬆理解時間複雜度(一)

原文連結:看動畫輕鬆理解時間複雜度(一) 演算法(Algorithm)是指用來操作資料、解決程式問題的一組方法。對於同一個問題,使用不同的演算法,也許最終得到的結果是一樣的,比如排序就有前面的十大經典排序和幾種奇葩排序,雖然結果相同,但在過程中消耗的資源和時間卻會有很大的區別,比如快速排序與猴子排

動畫輕鬆理解時間複雜度(二)

    上篇文章講述了與複雜度有關的大 O 表示法和常見的時間複雜度量級,這篇文章來講講另外幾種複雜度: 遞迴演算法的時間複雜度(recursive algorithm time complexity),最好情況時間複雜度(best case time complexity)、最壞情況

動畫輕鬆理解「 堆 」

堆(heap)又被為優先佇列(priority queue)。儘管名為優先佇列,但堆並不是佇列。 因為佇列中允許的操作是先進先出(FIFO),在隊尾插入元素,在隊頭取出元素。 而堆雖然在堆底插入元素,在堆頂取出元素,但是堆中元素的排列不是按照到來的先後順序,而是按照一定的優先順序

動畫動畫輕鬆理解「Trie樹」

Trie樹 Trie這個名字取自“retrieval”,檢索,因為Trie可以只用一個字首便可以在一部字典中找到想要的單詞。 雖然發音與「Tree」一致,但為了將這種 字典樹 與 普通二叉樹 以示區別,程式設計師小吳一般讀「Trie」尾部會重讀一聲,可以理解為讀「TreeE」。 Trie 樹,也

LeetCode 926. 將字串翻轉到單調遞增 實現動態規劃 兩種解法

這個題做了一個多小時,考慮複雜了。 開始推動規沒有推出來,然後找到一個遞推關係:從左往右,如果是0,則不需要變動;如果是1,則有兩種選擇(1)將1變為0(2)將1後面的所有數字變為1,這兩種方法中的變動數字最小的方法就是最佳方法,然後依次遞推,很容易寫出遞迴程式。但是這裡面存

HDU 2059 龜兔賽跑 VS動態規劃

龜兔賽跑原題如下: Problem Description 據說在很久很久以前,可憐的兔子經歷了人生中最大的打擊——賽跑輸給烏龜後,心中鬱悶,發誓要報仇雪恨,於是躲進了杭州下沙某農業園臥薪嚐膽潛心修煉,終於練成了絕技,能夠毫不休息得以恆定的速度(VR m/s)一直跑。兔子一直想找機

leetcode 746.爬樓梯的最小代價(從暴力動態規劃

題目: On a staircase, the i-th step has some non-negative cost cost[i] assigned (0 indexed). Once you pay the cost, you can either cl

LeetCode刷題Easy篇斐波那契數列問題(,尾,非動態規劃解法)

題目 斐波那契數列:  f(n)=f(n-1)+f(n-2)(n>2) f(0)=1;f(1)=1;  即有名的兔子繁衍問題  1 1 2 3 5 8 13 21 .... 我的解法 遞迴 public static int Recursion

LeetCode刷題Easy篇爬樓梯問題(動態規劃問題)

題目 You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb 1 or 2 steps. In how many distinc