課程:https://time.geekbang.org/dailylesson/detail/100028406javascript
問題:計算斐波那契數列的第n項的值,數列表達式:F[n]=F[n-1]+F[n-2] (n>=2,F[0]=0,F[1]=1)html
winter(講師)認爲這是一道很好的面試題,java
1. 答案簡單。面試
2. 每一個面試者都能寫出點東西。數組
3. 區分度高,不一樣水平的人寫出來的代碼水平不一樣。less
4. 馬甲衆多(不多有面試官直接上來講給我擼一個斐波那契數列,都會問一個具體的問題,最後歸根到底就是斐波那契數列問題)。函數
好,接下來看一下winter認爲不一樣水平的代碼長啥樣。性能
Lev1. 遞歸優化
int Fibnacci(int n){ if(n < 2){ return n; } return Fibnacci(n - 1) + Fibnacci(n - 2); }
能夠簡單算一下它的時間複雜度是指數級的。它會把子問題重複計算多遍。若是我要計算數列第5項的值,會計算以下中間結果。spa
能夠觀察到純在大量重複的節點計算。優化一下。
Lev2. 帶備忘錄的遞歸
int Fibnacci(int n){ if(map.ContainsKey(n)){ return map[n]; } if(n < 2){ return n; } int res = Fibnacci(n - 1) + Fibnacci(n - 2); map.Add(n, res); return res; }
時間複雜度O(n), 空間複雜度O(n)。到這有人說遞歸自帶性能消耗,再優化一下。
Lev3. DP (動態規劃)
當發現存在大量重複子問題的時候,一般咱們會想到DP.
首先咱們肯定狀態轉移方程 DP[n] = DP[n-1] + DP[n-2].
(DP是一種自下向上的解決問題的思路,先解出f(2), 那f(3)就得解,接着f(4)也就得解,直到f(n),而遞歸是自上而下)
int Fibnacci(int n){ if(n < 2){ return n; } int[] dp = new int[n + 1]; dp[0] = 0; dp[1] = 1; for(int i = 2; i <= n; i++){ dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; }
時間複雜度O(n), 空間複雜度O(n)。
能夠繼續優化把DP數組去掉,空間複雜度優化成O(1),這裏就不演示了。
到這,其實我認爲這已是極限了,最起碼是個人極限。
Lev4. 通項公式
沒錯,數學家給出了數列的通項公式,咱們老老實實套公式便可。
感興趣的能夠本身推導一遍。
let fibnacci = (n) => ((Math.pow(1 + Math.sqrt(5))/2, n) - Math.pow((1 - Math.sqrt(5))/2, n))/Math.sqrt(5);
問題來了,如今的時間複雜度是O(1)嗎?嚴格意義上說不是,這裏調用了系統的冪函數,winter沒有指出該函數在V8的具體實現,可是結論必定不是O(n), 更不是O(1)。
後面他提供了本身實現的O(log(N))冪運算版本:
let pow = (x, n) => { var r = 1; var v = x; while(n) { if(n % 2 == 1){ r *= v; n-= 1; } v = v * v; n = n /2; } return r; }
Lev N: ????
考慮到浮點偏差,winter再次提出藉助線性代數的矩陣運算來表示斐波那契數列的通項。
到這裏,我已經完全放飛自我,以爲他說的都對。
這節課的後半段,體驗不是很好,畢竟數學知識全還了,思路已經跟不上了。
1. 爬臺階:有個小孩正在上樓梯,樓梯有n階臺階,小孩一次能夠上1階、2階。實現一種方法,計算小孩有多少種上樓梯的方式(還有什麼青蛙跳臺問題,換一下主語)
2. 爬臺階+: 有個小孩正在上樓梯,樓梯有n階臺階,小孩一次能夠上1階、2階、3階。實現一種方法,計算小孩有多少種上樓梯的方式
3. 兔子繁殖問題: 一對兔子每月能生出一對小兔子來。若是全部兔子都不死,那麼一年之後能夠繁殖多少對兔子?
PS: 和斐波那契數列類似的著名數列:卡塔蘭數列
具體問題:給定一個整數 n,求以 1 ... n 爲節點組成的二叉搜索樹有多少種?
假如n=3,結果就是5種。