关于斐波那契数列的背景相信大家都有所耳闻,不知道的可以去搜索一下兔子问题。在大多数的应用场合,没有人会直接让你编写一段代码实现斐波那契数列,而会把他装在一个应用场景中,这个场景可以是兔子生兔子,也可以是青蛙爬楼梯,比起学会如何实现它,大家更需要掌握的技能是,看到应用场景可以反映出这实际上是一个斐波那契问题。毕竟如何实现斐波那契,网上的教程一搜一堆,但判断当前问题可不可以用斐波那契思想解决,需要你的判断才行。
言归正传,该问题的四种代码实现分别为 递归法、迭代法、通项公式法和矩阵法,后两种方法对于线性代数基础薄弱的人来说可能不太好理解,但是学会了它受益良多,还是建议大家能够掌握。
一、递归法
递归解决斐波那契问题虽然很直观,但存在的问题是时间复杂度太高(重复计算太多),所以给出代码各位看看就好。
class Solution {
public int climbStairs(int n) {
if(n == 0) return 0;
else if(n == 1) return 1;
else return climbStairs(n - 1) + climbStairs(n - 2);
}
二、迭代法
这里利用了动态规划的思想,从公式可以看出,当前的状态等于前一时刻的状态+前两时刻的状态,我们知道了第0时刻和第1时刻,递推就可以得到结果啦,显然时间复杂度为o(n)。
class Solution {
public int climbStairs(int n) {
int fibNOne = 1, fibNTwo = 0, fibN = 0;
for (int i = 1; i <= n; ++i) {
fibN = fibNOne + fibNTwo;
fibNTwo = fibNOne; //前两时刻
fibNOne = fibN; //前一时刻
}
return fibN;
}
}
三、通项公式
其实递归法已经足够好了,但是精益求精的我们会琢磨有没有时间复杂度更快地方法呢,还真有,o(logn)。
我们首先需要了解齐次线性递推公式的特征方程:
对于形如: 的递推式,可得结论:
1)其递推方程为
2)
故而由斐波那契数列可得齐次线性方程,解方程可得 ,
由 , ,可得
此时f(n)直接可求。
class Solution {
public int climbStairs(int n) {
return (int)((Math.pow((1+Math.sqrt(5))/2,n)-Math.pow((1-Math.sqrt(5))/2,n))/Math.sqrt(5));
}
}
这段代码的实现之所以是o(logn)原因就在于Math.pow这个函数,下面我们讲解一下这个函数的实现原理:
pow【快速幂算法】本质上是一个分治算法,故而时间复杂度是o(logn)。我们求实际上是在前一个结果的基础上进行平方操作。我们找到了问题的重复子问题,自然而然可以想到递归操作。有某些时刻,平方后的结果还需再乘上一个x。判断是否需要额外乘x的方法很简单,就是当n为奇数时,结果的平方需再乘以一个x。具体实现如下:
class Solution {
public double myPow(double x, int n) {
return n<0 ? 1/dfs(x, n) : dfs(x,n);
}
public double dfs(double x, int n) {
if(n==0) return 1.0;
double y = dfs(x,n/2);
return n%2==0 ? y*y : y*y*x;
}
}
另一个比较巧妙地思路是利用二进制位算法,我们利用规律:
则
具体实现如下:
lass Solution {
public double pow(double x, long n) {
double res = 1.0;
double x_contribution = x;
while(n>0) {
if((n&1)==1) res*=x_contribution;
x_contribution *= x_contribution;
n=n>>1;
}
return res;
}
public double myPow(double x, int n) {
long b = n; //防止执行n=-n时数组越界
return b<0 ? 1.0/pow(x,-b) : pow(x,b);
}
}
四、矩阵快速幂
第三种方法中的根号和除法会存在计算精度的问题。受到快速幂算法的启发,如果可以把求解过程转化为求解n次幂,那问题便可迎刃而解。这就要用到线性代数的知识了。
我们知道对于斐波那契数列,状态是这样转移的:
填0变成矩阵,则可得方程:
推导出:
此时求f(n)的问题便转化成了一个求矩阵n次幂的问题:
class Solution {
public int climbStairs(int n) {
int[][] q = {{0,1},{1,1}};
int[][] res = myPow(q,n);
return res[1][0];
}
public int[][] myPow(int[][] q, int n) {
int[][] res = {{1,0},{0,1}};
int[][] x_contribution = q;
if(n==0) return new int[2][2];
while(n>0) {
if((n&1)==1) res = multiple(res, x_contribution);
n = n>>1;
x_contribution = multiple(x_contribution,x_contribution);
}
return res;
}
public int[][] multiple(int[][] a, int[][] b) {
int[][] c = new int[2][2];
for(int i=0;i<2;i++) {
for(int j=0;j<2;j++) c[i][j] = a[i][0]*b[0][j]+a[i][1]*b[1][j];
}
return c;
}
}