509.斐波那契数
本题涉及内容:递归问题、动态规划问题、快速幂问题
题目链接https://leetcode-cn.com/problems/fibonacci-number/
递归
过于简单,直接看代码
int Fib(int n){
if(n<=0)return 0;
else if(n==1)return 1;
else return Fib(n-1)+Fib(n-2);
}//这里定义Fib(0)=0,Fib(1)=1;后续项为前两项之和
时间复杂度: O ( 2 n ) O(2^n) O(2n)
动态规划
注意自顶向下的方法对本题复杂度几乎没有任何优化,所以直接采用自底向上实现
int Fib(int n){
if(n==0)return 0;
else if(n==1)return 1;
else{
int p,q,i,tmp;
for(p=0,q=1,i=2;i<=n;i++){
tmp=q;
q=p+q;
p=tmp;
}
return q;
}
}
Tips:动态规划尽量采用自底向上实现,不仅时间复杂度稳稳小于自顶向下,而且有利于重构解。(自顶向下的唯一一个有点就是思路比较符合正常逻辑)
下面的才是重点
基于矩阵乘法的快速幂实现
先了解一下快速幂算法
快速幂的基本原理,就是通过将 x n x^n xn利用减少乘法次数的方式,将时间复杂度由 O ( n ) O(n) O(n)降低到 O ( l o g ( n ) ) O(log(n)) O(log(n))
下面简单介绍一个相关的例子:
3 10 = 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 → ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) → 9 ∗ 9 ∗ 9 ∗ 9 ∗ 9 → ( 9 ∗ 9 ) ∗ ( 9 ∗ 9 ) ∗ 9...... 3^{10}=3*3*3*3*3*3*3*3*3*3\rightarrow(3*3)*(3*3)*(3*3)*(3*3)*(3*3)\rightarrow9*9*9*9*9\rightarrow(9*9)*(9*9)*9...... 310=3∗3∗3∗3∗3∗3∗3∗3∗3∗3→(3∗3)∗(3∗3)∗(3∗3)∗(3∗3)∗(3∗3)→9∗9∗9∗9∗9→(9∗9)∗(9∗9)∗9......
KaTeX parse error: Expected 'EOF', got '&' at position 28: …3*3*3*3*3*3*3*3&̲普通计算方法:乘法次数9\\3…
时间复杂度:快速幂乘法次数实际上就是固定的平方乘法+幂为奇数是的额外乘法(可以不考虑),所以总时间复杂度为 O ( l o g ( n ) ) O(log(n)) O(log(n))
快速幂算法实现
int Fast_Pow(int x,int n){//返回x的n次
if(n==0)return 1;
else if(n%2==0){
int tmp=Fast_Pow(x,n/2);//注意保存tmp,这是优化关键
return tmp*tmp;
}
else return x*Fast_Pow(x,n-1));
}
矩阵乘法
通过如下转化图,可以看出矩阵求幂与斐波那契数列之间的关系
[ 1 1 1 0 ] [ F i b ( n − 1 ) F i b ( n − 2 ) ] = [ F i b ( n − 1 ) + F i b ( n − 2 ) F i b ( n − 1 ) ] = [ F i b ( n ) F i b ( n − 1 ) ] \begin{bmatrix}1&1\\1&0\end{bmatrix}\begin{bmatrix}Fib(n-1)\\Fib(n-2)\end{bmatrix}=\begin{bmatrix}Fib(n-1)+Fib(n-2)\\Fib(n-1)\end{bmatrix}=\begin{bmatrix}Fib(n)\\Fib(n-1)\end{bmatrix} [1110][Fib(n−1)Fib(n−2)]=[Fib(n−1)+Fib(n−2)Fib(n−1)]=[Fib(n)Fib(n−1)]
可见其中的递归性质,由此可以总结得到公式
F i b ( n ) = [ 1 1 1 0 ] n − 1 [ F i b ( 1 ) F i b ( 0 ) ] Fib(n)=\begin{bmatrix}1&1\\1&0\end{bmatrix}^{n-1}\begin{bmatrix}Fib(1)\\Fib(0)\end{bmatrix} Fib(n)=[1110]n−1[Fib(1)Fib(0)]
同理,那么3位的泰波那契数该怎么求呢 T r i b ( n ) = T r i b ( n − 1 ) + T r i b ( n − 2 ) + T r i b ( n − 3 ) Trib(n)=Trib(n-1)+Trib(n-2)+Trib(n-3) Trib(n)=Trib(n−1)+Trib(n−2)+Trib(n−3)
? ∗ [ T r i b ( n − 1 ) T r i b ( n − 2 ) T r i b ( n − 3 ) ] − > [ T r i b ( n ) T r i b ( n − 1 ) T r i b ( n − 2 ) ] ? = [ 1 1 1 1 0 0 0 1 0 ] ?*\begin{bmatrix}Trib(n-1)\\Trib(n-2)\\Trib(n-3)\end{bmatrix}->\begin{bmatrix}Trib(n)\\Trib(n-1)\\Trib(n-2)\end{bmatrix}\\?=\begin{bmatrix}1&1&1\\1&0&0\\0&1&0\end{bmatrix} ?∗⎣⎡Trib(n−1)Trib(n−2)Trib(n−3)⎦⎤−>⎣⎡Trib(n)Trib(n−1)Trib(n−2)⎦⎤?=⎣⎡110101100⎦⎤
所以只需将快速幂的算法带入矩阵乘法即可
int **Matrix_Mul(int **m1,int **m2){//2*2矩阵乘法
int **res=(int**)calloc(sizeof(int**),2);
res[0]=(int*)calloc(sizeof(int),2);
res[1]=(int*)calloc(sizeof(int),2);
res[0][0]=m1[0][0]*m2[0][0]+m1[0][1]*m2[1][0];
res[0][1]=m1[0][0]*m2[0][1]+m1[0][1]*m2[1][1];
res[1][0]=m1[1][0]*m2[0][0]+m1[1][1]*m2[1][0];
res[1][1]=m1[1][0]*m2[0][1]+m1[1][1]*m2[1][1];
return res;
}
int** Fast_Pow(int **x,int n){//返回x的n次
if(n==1)return x;
else if(n%2==0){
int **tmp=Fast_Pow(x,n/2);
return Matrix_Mul(tmp,tmp);
}
else return Matrix_Mul(x,Fast_Pow(x,n-1));
}
int Fib(int n){
if(n==0)return 0;
if(n==1)return 1;
int m=n-1;
int **x=(int**)calloc(sizeof(int**),2);
x[0]=(int*)calloc(sizeof(int),2);
x[1]=(int*)calloc(sizeof(int),2);
x[0][0]=x[1][0]=x[0][1]=1;x[1][1]=0;
int **res=Fast_Power(x,m);
return res[0][0];
}//这里定义Fib(0)=0,Fib(1)=1;后续项为前两项之和
最后附上运行时间的比较
小结
快速幂算法其实还能做一些小优化,之后的博客里会讲到