LeetCode 509 Fibonacci Number 斐波那契数 JAVA实现

题目描述: 

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

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

给定 N,计算 F(N)。

示例 1:

输入:2 输出:1 解释:F(2) = F(1) + F(0) = 1 + 0 = 1.

示例 2:

输入:3 输出:2 解释:F(3) = F(2) + F(1) = 1 + 1 = 2.

示例 3:

输入:4 输出:3 解释:F(4) = F(3) + F(2) = 2 + 1 = 3.

提示:

  • 0 ≤ N ≤ 30

解题思路:

(1)斐波那契数是一个典型的递归解决问题的模板,所以最简短的代码就是使用递归。

class Solution {
    public int fib(int N) {
        if(N <= 1)
            return N;
        return fib(N-1)+fib(N-2);
    }
}

接下来计算递归解法的时间复杂度和空间复杂度。

首先来波概念:

   递归算法的时间复杂度:递归的总次数*每次递归的数量。

   递归算法的空间复杂度:递归的深度*每次递归创建变量的个数。

递归算法时间复杂度分析

在递归调用过程中Fib(4)被计算了2次,Fib(3)被计算了3次。Fib(1)被调用了7次,Fib(0)中被调用了4次。所以,递归的效率低下,但优点是代码简单,容易理解。

 递归算法时间复杂度为(二叉树的节点个数):O()=(2^h)-1=2^n。空间复杂度为树的高度:h即o(n)。

(2)可用尾递归方法来求,尾递归若优化,空间复杂度可达到o(1),但时间复杂度是o(n);

尾递归是什么呢?

尾递归解决了递归重复计算的问题。

"尾递归的前提是递归"

(1)定义:在一个程序中,执行的最后一条语句是对自己的调用,而且没有别的运算

(2)尾递归的实现:是在编译器优化的条件下实现的

  编译器优化:

     递归的第一次调用时会开辟一份空间,此后的递归调用不会再开辟空间,而是在刚才开辟的空间上做一些修改,实现此次递归,例如在本题中求Fib(10),编译器会给Fib(10)的调用开辟栈帧,调用Fib(9)的时候不会再重新开辟栈帧,而是在刚开辟的栈帧上做一些修改,因为递归的每一次调用都是一样的流程,只是会有一些数据不同,所以不会再开辟空间。

注:vs一般都支持优化,Debug下编译器不会优化哦,一定要在Release模式下。

class Solution {
    public int fib(int N) {
        int res = f(1,1,N);
        return res;
    }
    public int f(int first, int second, int N){
        if(N == 0)
            return 0;
        if(N < 3)
            return 1;
        if(N == 3)
            return first + second;
        return f(second, first+second, N-1);
    }
}

此种方法是尾递归,很大程度的减小了第一种方法(递归实现斐波那契数列)的时间复杂度

时间复杂度:O(N-2)约等于0(N)

空间复杂度:O(N-2)约等于0(N)(编译器如果优化的话是O(1))

(3)采用循环结构来实现,此时时间复杂度为O(n),空间复杂度为O(1)

循环结构的实现有几种方式,第一种是采用两个辅助变量来分别记住Fib[i-1]和Fib[i-2]。

class Solution {
    public int fib(int N) {
        if(N == 0)
            return 0;
        if(N == 1)
            return 1;
        int temp1 = 0;
        int temp2 = 1;
        int res = 0;
        for(int i=2;i<=N;i++){
            res = temp1 + temp2;
            temp1 = temp2;
            temp2 = res;
        }
        return res;
    }
}

也可以只使用一个辅助变量

class Solution {
    public int fib(int N) {
        if(N == 0)
            return 0;
        if(N == 1)
            return 1;
        int temp = 0;
        int res = 1;
        for(int i=2;i<=N;i++){
            res += temp;
            temp = res - temp;
        }
        return res;
    }
}

还可以使用辅助数组,将每次计算的结果存储在数组里面,这样空间复杂度也变为O(n)

class Solution {
    public int fib(int N) {
        int f[] = new int[N+1];
        f[0] = 0;
        if (N > 0) {
            f[1] = 1;
        }

        for (int i = 2; i <= N; i++) {
            f[i] = f[i-1] + f[i-2];
        }
        return f[N];
    }
}

从效率的角度考虑,采用循环结构是最优的。优点是时间复杂度和空间复杂度最低,而且可读性高。

最后,常用时间复杂度所耗费的时间从小到大依次是o(1)<o(log2n)<o(n)<o(nlog2n)<o(n^2)<o(n^3)<o(2^n)<o(n!)<o(n^n)。

最后,复习一下主定理计算递归的时间复杂度。

 https://baike.baidu.com/item/%E4%B8%BB%E5%AE%9A%E7%90%86/3463232?fr=aladdin

猜你喜欢

转载自blog.csdn.net/qq_25406563/article/details/87170398