斐波那契数列

177 阅读2分钟

题目:

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n) 。


暴力破解

斐波那契数列的数学形式就是递归的,写成代码为:

class Solution {
    public int fib(int n) {
        if(n == 0) return 0;
        if(n == 1 || n ==2) return 1;
        return fib(n - 1) + fib(n - 2);
    }
}

虽然暴力破解可以解决此问题,但是在计算中会产生大量冗余,以fib(20)为例:

暴力破解.png

其中fib(18)、fib(17)进行多次复用,计算量巨大。 以时间复杂度说明,子问题个数为树中节点的组数为O(n2n^2),一个子问题解决时间为fib(n-1)+fib(n-2)即为O(1),两者相乘时间复杂度为O(n2n^2)。

带"备忘录"的递归解法

既然重复的原因是因为重复计算,则可以创建一个备忘录,将每次计算后的数值记录到备忘录中,在子问题计算前查看备忘录是否含有值,可以减少计算。

class Solution {
    public int fib(int n) {
        int[] memo = new int[n+1];
        return helper(memo, n);
    }
    public int helper(int[] memo, int n){
        if(n == 0 || n == 1) return n;
        if(memo[n] != 0) return memo[n];
        memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
        return memo[n];
    }
}

此时计算过的fib(n)存入至memo数组中不必在进行重复计算,减少了子问题个数。子问题的个数减少至n,时间复杂度为O(N)。这是自顶向下的递归做法。

dp数组的递推解法

dp数组的递归方法为自底向上的解法,从最底下、最简单、问题规模最小向上推,直到达到所求解。

class Solution {
    public int fib(int n) {
        if(n == 0) return 0;
        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];
    }
   
}

只需要计算dp[n]数组的子问题,即可遍历完成。 但其实我们每次只需要前两个dp数组的值,dp数组过于冗余,因此可以简化为

class Solution {
    public int fib(int n) {
        if(n == 0 || n == 1) return n;
        int dp_i_1 = 1, dp_i_2 = 0;
        for(int i = 2; i <= n; i++){
            int dp_i = dp_i_1 + dp_i_2;
            dp_i_2 = dp_i_1;
            dp_i_1 = dp_i;
        }
        return dp_i_1;
    }
}

将空间复杂度降低为O(1)。


解题思路参考labuladong的算法笔记1.3.1章。