课程: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种。