[极客时间-每日一课]如何优雅地计算斐波那契数列?

课程:https://time.geekbang.org/dailylesson/detail/100028406

问题:计算斐波那契数列的第n项的值,数列表达式:F[n]=F[n-1]+F[n-2] (n>=2,F[0]=0,F[1]=1)

winter(讲师)认为这是一道很好的面试题,

  1. 答案简单。

  2. 每个面试者都能写出点东西。

  3. 区分度高,不同水平的人写出来的代码水平不同。

  4. 马甲众多(很少有面试官直接上来说给我撸一个斐波那契数列,都会问一个具体的问题,最后归根到底就是斐波那契数列问题)。

 

好,接下来看一下winter认为不同水平的代码长啥样。

 

Lev1. 递归

int Fibnacci(int n){
    if(n < 2){
        return n;
    }
    return Fibnacci(n - 1) + Fibnacci(n - 2);
}

可以简单算一下它的时间复杂度是指数级的。它会把子问题重复计算多遍。如果我要计算数列第5项的值,会计算如下中间结果。

 

 

 可以观察到纯在大量重复的节点计算。优化一下。

 

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种。

卡特兰的随笔已补:https://www.cnblogs.com/benzhai/p/12423769.html

posted on 2020-03-04 17:18  qingMing01  阅读(958)  评论(0编辑  收藏  举报

导航