同学们,下方传送门:青蛙跳台阶的问题
首先看到这道题后,我们能够自己继续递推:
0,1,1,2,3,5,8...
同时,能够得到n,n-1,n-2
之间的关系:
F(n)=F(n-1)+F(n-2)
再配合上面给到的初始条件,第一种思路也就产生了——递归。
思路1:递归
想到递归,先找到构建递归的条件:
- 终止条件
- 本问题和子问题解的关系
以上问题,我们都有答案,直接上代码:
class Solution {
public int fib(int n) {
if(n==0) return 0;
if(n==1) return 1;
return fib(n-1)+fib(n-2);
}
}
递归构建非常清晰,不过很遗憾,答案超时
为什么超时呢?
这是因为斐波那契数列特殊的机制:
F(n)=F(n-1)+F(n-2);
F(n-1)=F(n-2)+F(n-3);
F(n-2)=F(n-3)+F(n-4);
能够看出,子问题的解答过程中,有很多多余的计算过程,也就是很多结果之前算过了。
结合时间复杂度分析:
假设想求F(3)
,需要计算下层所有的值。
相当于一个三层的完全二叉树,求其所有节点个数,因此时间复杂度为O(2^n)
因此就有了我们下面两种基于迭代(循环的方法)
思路2:带中间变量的迭代
上面这种方法的问题在于,中间步骤经历了太多次重复计算。
那么如果我们能够将计算过的结果重复利用,就能很好地舍去计算的过程,就能大大提高性能
上代码:
class Solution {
public int fib(int n) {
if(n<=1) return n;
int a=1;
int b=0;
int ans=0;
for(int i=2;i<=n;i++) {
ans=(a+b)%1000000007;
b=a;
a=ans;
}
return ans;
}
}
跟递归一样,我们开始进行一个基本的判断。
对于大于1的数,我们迭代来求最终的结果
由于斐波那契数列的性质不变:F(n)=F(n-1)+F(n-2);
因此我们用两个变量,来记录一直变化的那两个加数
如上代码中:
第一次for
循环,计算的是F(2)
,我们都知道F(2)=F(1)+F(0)
,而变量a刚好是F(1)
,b刚好是F(0)
重点来了!!!!
通过一轮计算后,我们需要让a
表示F(n-1)
,b表示F(n-2)
,因此在计算下一轮之前,把F(2)
的值赋值给a
,把之前a
的值赋值给b
,再继续下轮循环
这种方法,将他的时间复杂度从O(2^n),变成了O(n),只是for循环的次数
思路3:利用数组存储数据,迭代
这个解法的整体思路和上一种很相似,只不过把变量变成了数组
当遇到已经计算过的数据的时候,直接去数组中取即可!
代码如下:
class Solution {
public int fib(int n) {
if(n<2) {
return n;
}
int[] ans=new int[n+1];
ans[0]=0;
ans[1]=1;
for(int i=2;i<=n;i++) {
ans[i]=(ans[i-1]+ans[i-2])%1000000007;
}
return ans[n];
}
}
由于有数组的存在,这种方法的空间复杂度变成了O(n)