斐波那契数列

1,324 阅读3分钟

斐波那契数列

什么是斐波那契数列?

数列第1和第2项为1,从第3项开始,每一项都等于前两项之和
112358132134、...,n 

求斐波那契数列的第 n 项
  • 1、初级解法
/*
1.数列的第1项和第2项是确定的,所以当输入的 n 为1或者2时直接返回1即可
2.数列的第3项开始每一项都是前两项之和,所以比如传传入的 n 为5时,第5项的结果为第4项和第3项之和,而第4项又是第3项和第2项之和,以此类推
*/
function fib(n) {
    if (n===1 || n===2) {
        return 1;
    }
    return fib(n-1) + fib(n-2);
}

  • 时间复杂度:可以从图中看出我们要求第5项的话可以分解为第4项和第3项之和,由此类推。每一个父项都会裂变为2个子项。而树的高度为 n 减1,那么这里时间复杂 O(n) 为2的 n 次方
  • 空间复杂度:这里假如要求第5项,那么执行栈的要从求第4项-->第3项-->第2项,到达第2项以后即到达出口,那么这里压栈的深度为n-1, 即空间复杂度 O(n) 为 n
我们可以看到,随着 n 的增长,计算第 n 项所需要的时间的是爆炸性的指数级增长,这是很难以接受的
  • 2、带备忘录的斐波那契数列解法

我们可以发现,为了计算第 5项,我们共需要计算1次第4项,2次第3项。我们可以想象一下,假如我们要找第9999项,那么我们肯定需要计算非常多次第3项的,然而这些计算真的是有必要的吗?而正是这些重复的计算就导致了该算法时间复杂度的爆炸性增长。那么知道问题的话解决办法也很简单,只要我们把以前计算的结果缓存起来即可

const fib = (function () {
    const memo = {
        1: 1,
        2: 1,
    };
    return function _getFib(n){
        if (memo[n]) {
            return memo[n];
        }
        const res = _getFib(n-1) + _getFib(n-2);
        memo[n] = res;
        return memo[n]
    }
})()

  • 时间复杂度:由图2可以看出如果要求第5项,我们不再需要一直裂变下去,而是变成了一条直线,因为每次即将裂变的时候我们都可以在缓存里面找到对应的值,时间复杂度直接变为了O(n),已经和没有优化之前的解法有了本质区别。
  • 空间复杂度:这里空间复杂度还是没有改变的,当然空间复杂度也是有办法降低的,需要动态规划。

我们可以看出想要求出第 n 项,那么我们必须得知第 n-1 和 n-2 项,以此类推。那么第 n 项便分解为求 n-1 和 n-2 项的子问题,而求第 n-1 的解法和第 n 项是没有区别的。就是说第 n 项的数据结构和第 n-1 项的数据结构是完全相同的。这就是为什么我们可以把求第n项分解为求n-1项。这也是递归的本质,把父问题分解为相同数据结构的子问题。