LeetCode 509 斐波那契数
LeetCode链接
斐波那契数(通常用F(n)表示)的定义:
F(0)=0,F(1)=1,
F(n)=F(n-1)+F(n-2),n>1
由斐波那契数构成的序列称为斐波那契数列,它由0,1开始(正宗的斐波那契数列应该是以1,1开始,没有F(0)这一项),之后的每一项都是前两项之和。
题目要求给定一个数n,计算F(n)的值。(0<=n<=30)
斐波那契数列因数学家莱昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”。
一般而言,兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔子都不死:
我们不妨拿新出生的一对小兔子分析一下:
第一个月小兔子没有繁殖能力,所以还是一对
两个月后,生下一对小兔对数共有两对
三个月以后,老兔子又生下一对,因为小兔子还没有繁殖能力,所以一共是三对
------
依次类推可以列出下表:
所以能得出以下公式:
幼仔对数=前月成兔对数
成兔对数=前月成兔对数+前月幼仔对数=前月总体对数
总体对数=本月成兔对数+本月幼仔对数=前月总体对数+前月成兔对数=前月总体对数+前前月总体对数 //F(n)=F(n-1)+F(n-2)的来源
//希望这几个公式没有把你绕晕
方法一、递归
斐波那契序列是典型的递归问题。除了头两项以外,其他F(n)的值取决于F(n-1),F(n-2)。而F(n-1),F(n-2)又取决于它们前两项的值,以此类推,在此我就不过多赘述。代码如下:
//递归
class Solution {
public int fib(int n) {
if(n<2) {
return n;
}
return fib(n-1)+fib(n-2);
}
}
方法二、动态规划
递归的代码虽然简捷明了,看起来赏心悦目,但函数调用的次数很多,开销很大,而且它里面进行了很多重复计算。举个例子 ,如下图,计算F(9)时,F(7)要计算2次,F(6)要计算3次…n越大,重复计算的次数就越多,时间复杂度为O(2^n),指数爆炸,这并不可取。
如果将每次计算的F(n)存起来,那么将会大大减少计算的次数,这就是动态规划的思想。状态转移方程就是F(n)=F(n-1)+F(n-2),边界条件就是F(0)=0,F(1)=1,用dp[n]存储F(n)。代码如下:
class Solution {
public int fib(int n) {
if(n<2) {
return n;
}
int[] dp=new int[n+1];
//边界条件,dp数组的初始化
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
因为最后结果只要求返回F(n),而F(n)只与F(n-1)、F(n-2)有关,所以,上面空间复杂度为O(n)的方法还可以优化,利用滚动数组的思想,用两个变量存储F(n-1)、F(n-2),将空间复杂度降为O(1)。代码如下:
//动态规划
class Solution {
public int fib(int n) {
if(n<2) {
return n;
}
//p,q是边界F(0)、F(1)
int p=0;
int q=1;
int res=0;
for(int i=1;i<n;i++){
res=p+q;
p=q;
q=res;
}
return res;
}
}
方法三、公式法
这里直接粘贴官方题解中有关公式法的介绍
第一次看完我大受震憾,只能说自己实力不够还得好好学习吧。代码如下:
class Solution {
public int fib(int n) {
double temp=Math.sqrt(5);
double ans=(1/temp)*(Math.pow((1+temp)/2,n)-Math.pow((1-temp)/2,n));
return (int)Math.round(ans);
//round()函数参数为double型时,返回一个最接近该参数的long型数,参数为float型时,返回一个最接近的int值
}
}
类比
该题与LeetCode 70 题 爬楼梯 LeetCode链接 十分相似。同时,LeetCode 剑指offer 10-I 斐波那契数列 LeetCode链接 中有当n很大时关于取模的处理,都可以尝试做一下。