假設你正在爬樓梯。須要 n 階你才能到達樓頂。
每次你能夠爬 1 或 2 個臺階。你有多少種不一樣的方法能夠爬到樓頂呢?
注意: 給定 n 是一個正整數。
示例 1:java
輸入: 2
輸出: 2
解釋: 有兩種方法能夠爬到樓頂。
1.1 階 + 1 階
2.2 階算法
示例 2:測試
輸入: 3
輸出: 3
解釋: 有三種方法能夠爬到樓頂。
1.1 階 + 1 階 + 1 階
2.1 階 + 2 階
3.2 階 + 1 階優化
不妨舉個例子,假設須要5階才能到達樓頂,f(5)爲到達樓頂的方法數。那麼要到達第5階,只可能有2種狀況:1.從第4階爬1個臺階;2.從第3階爬2個臺階;這就把問題轉化爲求f(4)和f(3),即f(5) = f(4) + f(3),因此f(n) = f(n - 1) + f(n - 2)。網站
class Solution { public int climbStairs(int n) { //f(1) = 1, f(2) = 2; //f(n) = f(n - 1) + f(n - 2); if(n == 1) return 1; if(n == 2) return 2; return climbStairs(n - 1) + climbStairs(n - 2); } }
代碼雖然簡單,可是咱們畫出遞歸樹後會發現有不少冗餘項,即不少重複計算,例如求f(11),須要求 f(10) + f(9),求f(10)又須要求f(9) + f(8),求f(9)又須要求f(8) + f(7),這裏f(9)和f(8)分別計算了兩次,很是耗時,其時間複雜度爲O(2^n),提交也不經過。下面我將介紹兩種基於此算法的優化算法。code
上文咱們分析到遞歸會有大量重複計算,事實上遞歸過程是有前後順序的,若是先前計算的結果能夠存儲,那麼後面須要用到的話就能夠直接拿去用了,這樣時間複雜度降爲O(n),無疑是一次降維打擊。遞歸
class Solution { public int climbStairs(int n) { int[] meno = new int[n + 1]; //存儲每一次的結果,初始全爲0 return __climbStairs(n, meno); } public int __climbStairs(int n, int[] meno){ if(n == 1) return 1; if(n == 2) return 2; if(meno[n] > 0) return meno[n]; //大於0就證實已經計算過了,直接返回 meno[n] = __climbStairs(n - 1, meno) + __climbStairs(n - 2, meno); return meno[n]; } }
不難發現,這個問題能夠被分解爲一些包含最優子結構的子問題,即它的最優解能夠從其子問題的最優解來有效地構建,咱們能夠使用動態規劃來解決這一問題。令 dp[i] 表示能到達第 i 階的方法總數,從上文的分析中易知該動態轉移方程爲dp[i] = dp[i - 1] + dp[ i - 2];leetcode
public class Solution { public int climbStairs(int n) { if (n == 1) { return 1; } int[] dp = new int[n + 1]; dp[1] = 1; dp[2] = 2; for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } }
若是單從經過這道題目來講,打表無疑是另闢蹊徑,既然網站有時間限制,並且測試用例很適合打表,那麼我在本地把每一種階梯的方法數求出來,用一個容器存儲,傳入哪一個階梯我就有哪一個答案,時間複雜度爲O(1)。get
public class Solution { public int climbStairs(int n) { int result = 0; switch(n){ case 1: result = 1; break; case 2: result = 2; break; case 3: result = 3; break; case 4: result = 5; break; case 5: result = 8; break; case 6: result = 13; break; case 7: result = 21; break; case 8: result = 34; break; case 9: result = 55; break; case 10: result = 89; break; case 11: result = 144; break; case 12: result = 233; break; case 13: result = 377; break; case 14: result = 610; break; case 15: result = 987; break; case 16: result = 1597; break; case 17: result = 2584; break; case 18: result = 4181; break; case 19: result = 6765; break; case 20: result = 10946; break; case 21: result = 17711; break; case 22: result = 28657; break; case 23: result = 46368; break; case 24: result = 75025; break; case 25: result = 121393; break; case 26: result = 196418; break; case 27: result = 317811; break; case 28: result = 514229; break; case 29: result = 832040; break; case 30: result = 1346269; break; case 31: result = 2178309; break; case 32: result = 3524578; break; case 33: result = 5702887; break; case 34: result = 9227465; break; case 35: result = 14930352; break; case 36: result = 24157817; break; case 37: result = 39088169; break; case 38: result = 63245986; break; case 39: result = 102334155; break; case 40: result = 165580141; break; case 41: result = 267914296; break; case 42: result = 433494437; break; case 43: result = 701408733; break; case 44: result = 1134903170; break; case 45: result = 1836311903; break; } return result; } }
從咱們分析出來的遞歸公式能夠看出這就是一個求斐波那契數的問題,那麼根據斐波那契公式(公式經過求根公式或者數列的遞推式能夠求出)咱們能夠直接求得結果。it
public class Solution { public int climbStairs(int n) { double sqrt5=Math.sqrt(5); double fibn=Math.pow((1+sqrt5)/2,n+1)-Math.pow((1-sqrt5)/2,n+1); return (int)(fibn/sqrt5); } }
參考文章:https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode/