LeetCode 509 斐波拉契数(Java 版)




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

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

斐波拉契数列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

题目

给定 N,计算 F(N)

递归

在很多大学教材里边,都会把计算斐波拉契数列作为熟悉递归的入门程序。
F(N) 的时候我们只需要取调用 return F(N-1) + F(N-2) 即可。所以我们可以写出如下的代码:

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

递归是函数调用自己本身,调用的时候解释器或者编译器会为它分配栈空间,如果递归层数过高,很容易导致栈空间溢出 StackOverFow。

上面的那个程序,我们画一张图:
递归法求斐波拉契数列 F(8),会有大量重复的子递归
计算一个 F(8) 可能会重复递归多次 F(4) 等。这样很容易导致栈的数量过多,耗时巨大。不信你可以计算一个 F(100),计算时间非常长。

记忆化递归

将递归过的结果记录下来。比方说如果F(4)计算过了,就把F(4)的值存入数组中,直接拿来用就好了。

class Solution {
    Integer[] data;
    public int fib(int N) {
        if(N<=1){
            return N;
        }
        data = new Integer[N+1];
        data[0] = 0;
        data[1] = 1;
        return helper(N);
    }

    public int helper(int N){
        if(data[N] != null){
            return data[N];
        }
        data[N] = helper(N-1)+helper(N-2);
        return data[N];
    }
}

这里需要注意,在Java中全局变量,默认是有初值的。int[] 的初值是{0,0,0},Integer[]的初值是{null, null, null}

自底而上记忆化递归

刚才使用的方法,先计算的是 F(N),然后计算 F(N-1)+F(N-2),程序会根据书写顺序先对 F(N-1)进行递归。因此,每次递归的时候实际上先去递归了 F(K-1)+F(K-2) 中的左边 F(K-1),然后 F(K-1) = F(K-1-1) + F(K-1-2) 然后又会去递归 F(K-1-1)。这样下来,当计算到F(0)的时候,最里层递归结束,才会真正地将值保存起来,即去保存 F(2) = F(1)+F(0)。然后是 F(3) = F(2) + F(1)。

如果,我们直接从 F(2) 开始算,这样从一开始就可以保存 F(2) 及之后的值了

class Solution {
    int[] data;
    public int fib(int N) {
        if(N<=1){
            return N;
        }
        data = new int[N+1];
        data[0] = 0;
        data[1] = 1;
        memorize(2, N);
        return data[N];
    }

    public void memorize(int cur, int N){
        if(cur <= N){
            // 例如:f(2) = f(1) + f(0)
            data[cur] = data[cur-1] + data[cur-2];
            // 去计算f(3);
            memorize(cur+1, N);
        }
    }
}
迭代法

实际上,递归一般都可以转换为迭代。在一个方法体内部就可以完成递归干的事情。

思路的来源,就是之前的 自底而上的记忆化递归。先计算 F(2) 再根据 F(2) 计算 F(3) …

尝试将其转换为一个循环体:

  • 对于 <=1 的计算直接返回
  • 然后 for 循环从2开始,
class Solution {
    public int fib(int N) {
        if(N<=1){
            return N;
        }
        // 如果没有返回,此时的 N 从 2 开始,我们最少会考虑到 F(2) ~ F(N) 的情况
        int a = 0;
        int b = 1;
        // i = 2 的时候计算的是 f(0) + f(1) ==> a + b
        // i = N 的时候计算的是 f(N-2) + f(N-1) 
        for(int i=2; i<=N; i++){
            // 相当于往右移一位
            int temp = a + b;
            a = b;
            b = temp;
        }
        return temp;
    }
}

另一种比较简洁的写法

class Solution {
    public int fib(int n) {
        int a = 0, b = 1, sum = 0;
        for (int i = 0; i < n; i++) {
            sum = (a + b) % 1000000007;
            a = b;
            b = sum;
        }
        // 这里注意第一次执行 0+1 等价于 F(2)
        // 想要F(n)的结果,则需要执行到(n-2)+(n-1) = F(n)
        // 而这个循环会执行到(n-1) + n = F(n+1)
        // a 存储的是上一次循环的结果
        return a;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值