斐波拉契数的定义
斐波那契数,通常用 F(n)
表示,形成的序列称为斐波那契数列。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
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(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;
}
}