题目
斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0, F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
给定 N,计算 F(N)。
思路
利用一个递归调用,就可以做解答了。
代码
class Solution {
public int fib(int N) {
int result=0;
if(N==0)
result=0;
if(N==1)
result=1;
if(N>=2)
result=fib(N-1)+fib(N-2);
return result;
}
}
结果
只是利用了一次递归,这样子就会很慢。不好
改进一下
因为利用递归,有一些数值需要重复计算多次,比如N=20的时候,F(19),和F(18)都会计算到F(17),所以重复计算,时间花销很大,我们利用一个数组来存放。
改进代码
class Solution {
public int fib(int N) {
if (N==0)
return 0;
else if (N==1)
return 1;
else
{
int result[]=new int[N+1];
result[0]=0;
result[1]=1;
for (int i=2;i<=N;++i)
result[i]=result[i-1]+result[i-2];
return result[N];
}
}
}
结果
经过这样一改进,就变得速度快很多了,但是这个东西,还可以有更加高效的
再改进思路
我们注意到,其实每一个数据都只是跟他前两个数据有关系,即后面的计算跟前面的数据是没有关系的,所以我们可以不必使用这个数组,改为三个整形变量来存储结果即可。
再改进代码
class Solution {
public int fib(int N) {
if (N<=1)
return N;
else
{
int first=0;
int second=1;
int result=1;
for (int i=2;i<N;++i)
{
first=second;
second=result;
result=first+second;
}
return result;
}
}
}
结果
跟数组差不多,应该是开一个固定数组的花销也不是很大。
再再改进思路
利用矩阵快速幂来解决,这个算是非常妙的一种方法了【要有线性代数的基础】
F(N)=F(N-1)+F(N-2);变为F(N)=F(N-1)*1+F(N-2)*1;这样,我们利用一个基础的矩阵
[[1,1],[1,0]]就可以实现上面的情况了。最后,我们只需要返回结果矩阵的[0][0]位置的数值即可。
再再改进代码
class Solution {
public int fib(int N) {
if (N<=1)
return N;
//基础矩阵
int base[][]={
{1,1},
{1,0}
};
//结果矩阵
int result[][]={
{1,1},
{1,0}
};
//本来就是要基础矩阵连乘N-1次,然后现在,因为结果矩阵的左上角已经是F(2)的结果,已经给出来了,所以,实际需要连乘 N-2次。
for (int i=2;i<N;++i)
{
multiply(result,base);
}
return result[0][0];
}
//矩阵相乘
public void multiply(int[][] A, int[][] B) {
int x = A[0][0] * B[0][0] + A[0][1] * B[1][0];
int y = A[0][0] * B[0][1] + A[0][1] * B[1][1];
int z = A[1][0] * B[0][0] + A[1][1] * B[1][0];
int w = A[1][0] * B[0][1] + A[1][1] * B[1][1];
A[0][0] = x;
A[0][1] = y;
A[1][0] = z;
A[1][1] = w;
}
}
结果
这个就是利用矩阵来做得计算,这个就已经是很快了。
再再再改进思路
上面利用矩阵来计算,是一种很快的方法,但是,我们是每次都乘上一个矩阵,要乘N-1次,那么有什么办法可以加快这种乘法吗?有的,就是利用,矩阵快速幂。
一个矩阵,连乘N-1次,N-1可以分解为多个 2的n次方相加,就是这个数,去转化为一个二进制数,当二进制数为1的时候,那个矩阵的2的n次方就需要进入到结果之中,我们就把结果与这个n次方相乘,这就可以减少我们做矩阵乘法的时间。
所以,我们可以利用这个思路,来消减我们做乘法的次数,这样子,更快!
再再再改进代码
class Solution {
public int fib(int N) {
if (N<=1)
return N;
//基础矩阵
int base[][]={
{1,1},
{1,0}
};
//结果数组
int result[][]={
{1,1},
{0,0}
};
int test=N-2;//实际需要矩阵相乘的次数
while(test>0)
{
if((test&1)==1) multiply(result,base);//矩阵的 2的n次方是否需要进入到结果中
multiply(base,base);//计算矩阵的两次,四次,。。。。
test>>=1;
}
return result[0][0];
}
//两个矩阵相乘
public void multiply(int[][] A, int[][] B) {
int x = A[0][0] * B[0][0] + A[0][1] * B[1][0];
int y = A[0][0] * B[0][1] + A[0][1] * B[1][1];
int z = A[1][0] * B[0][0] + A[1][1] * B[1][0];
int w = A[1][0] * B[0][1] + A[1][1] * B[1][1];
A[0][0] = x;
A[0][1] = y;
A[1][0] = z;
A[1][1] = w;
}
//把矩阵B赋值给矩阵A
public void voluation(int A[][],int B[][])
{
A[0][0]=B[0][0];
A[0][1]=B[0][1];
A[1][0]=B[1][0];
A[1][1]=B[1][1];
}
}
结果
这个就是算法的力量了,用到了一下矩阵快速幂,然后,速度又快,而且内存少,时间短。
题解利用数学公式
这个就真的想不到了,居然还能这样子做,只能给大佬喊666了!
题解数学代码
class Solution {
public int fib(int N) {
double goldenRatio = (1 + Math.sqrt(5)) / 2;
return (int)Math.round(Math.pow(goldenRatio, N)/ Math.sqrt(5));
}
}
结果
哈哈,虽然这种方法贼秀,但是还是比矩阵快速幂慢,真香!
总结
算法之路无穷尽也,只要多思考,多练习,收获还是会逐步增加的!跟大佬的差距真的不是一般的大,只能慢慢跟上了!
也是通过这个斐波那契数列,终于自己亲手实现了一此矩阵快速幂,使之变为我自己的东西,也顺带理解了快速幂。当年欠下的债,还是要自己亲手还回来!