题面描述
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。
n<=39
斐波哪契数列
一般而言,兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔都不死,那么一年以后有多少对兔子?
我们可以把这些兔子的数量以对为单位列出数字就能得到一组数字:1、1、2、3、5、8、13、21、34、55、89、144、233。所以,过了一年之后,总共会有233对兔子了。那么续往下呢?
其实这组数字可以形成一个有规律的数列,这个数列的规律是这样的:它的第一项、第二项是1,而从第三项起每一项都等于它的前两项之和。这个数列是在1228年意大利数学家斐波那契首先提出的。我们把这个数列叫做“斐波那契数列”。在这个数列中的数字,就被称为“斐波那契数”。
设f(n)表示斐波那契数列中的第n个数,则
那么就可以很容易的写出递归代码,因为题目中说明请你输出斐波那契数列的第n项(从0开始,第0项为0)。
class Solution {
public:
int Fibonacci(int n) {
if(n==0)
{
return 0;
}
if(n==1||n==2)
{
return 1;
}
else{
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
};因为f(0)=0,f(1)=1,f(2)=f(0)+f(1),所以也可以把代码合并一下
class Solution {
public:
int Fibonacci(int n) {
if(n<2)//小于2的时候f(n) = n
{
return n;
}
else{
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
};但是提交发现合并后的代码超时,因合并后的代码多计算了一层,f(2) = f(0)+f(1),而原来的代码是直接返回的。多计算的这一层有这么大的影响么?
我们来观察这个递归的时间复杂度
以n=6为例做递归决策树
我们不难发现在这棵树中有很多结点是重复的,而且重复的结点数会随着n的增加而急剧增加,这意味计算量会随着n的增加而急剧增大。
可以发现复杂度肯定是指数级的。
具体复杂度的计算
证明网址:https://www.geeksforgeeks.org/time-complexity-recursive-fibonacci-program/
结果为
现在就遇到了一个问题,怎么样才能避免重复计算呢?
我们建立一个数组,把算出的答案存到数组里,使用的时候直接取就可以了。
class Solution {
public:
int dp[10000];
int Fibonacci(int n) {
if(dp[n])
{
return dp[n];
}
if(n<2)//小于2的时候f(n) = n
{
return n;
}
else if(n==2){
return 1;
}
else{
return dp[n]=Fibonacci(n-1)+Fibonacci(n-2);
}
}
};优化以后的递归决策树如下
这种思路其实就是记忆化搜索算法
记忆化搜索简介
记忆化搜索实际上是递归来实现的,但是递归的过程中有许多的结果是被反复计算的,这样会大大降低算法的执行效率。
而记忆化搜索是在递归的过程中,将已经计算出来的结果保存起来,当之后的计算用到的时候直接取出结果,避免重复运算,因此极大的提高了算法的效率。
继续对这个问题进行思考,我们知道和
以后,可以推出
,然后可以推出
,之后可以推出
....所以我们直接算到n,就可以得出答案了
class Solution {
public:
int Fibonacci(int n) {
int dp[10000];
dp[1] = dp[2] = 1;
for(int i = 3 ; i <= n ;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};继续对这个问题进行思考,我们知道和
以后,可以推出
然后可以推出
之后可以推出
....所以我们一直推算到
,就可以得出答案了
C++版本代码:
class Solution {
public:
int Fibonacci(int n) {
if(n==0){return 0;}
if(n==1||n==2){return 1;}
int f1,f2,ans;
f1=f2=ans=1;//f1,f2分别表示f(n-1)和f(n-2)
for(int i = 3 ; i <= n ;i++)//从f(3)递推到f(n)
{
ans = f1+f2;
f1 = f2;
f2 = ans;
}
return ans;
}
};python版本代码:
class Solution:
def Fibonacci(self, n):
if n==0:
return 0
if n==1 or n==2:
return 1
f1 = f2 = ans = 1
i = 3
while(i<=n):
ans = f1+f2
f1 = f2
f2 = ans
i+=1
return ans扩展:
对于本题的数据范围:𝑛 ≤39 ,时间复杂度𝑂(𝑛)的算法已经足够了。
但是如果我把题目进行一下扩展:
数据范围 𝑛≤10^18,求𝐹𝑖𝑏𝑜𝑛𝑎𝑐𝑐𝑖(𝑛) % 1000000009 的结果
因为n的范围很大,求出的斐波那契数也会很大,为了避免大数计算,出题时一般会让求解答案%某个素数的结果
取模运算满足以下性质,所以在计算的过程中可以先取模在进行运算,来保证数据一直处于非溢出的范围内
(a + b) % p = (a%p + b%p) %p
(a – b) % p = ((a%p – b%p) + p) %p
(a * b) % p = (a%p)*(b%p) %p。
对于扩展后的题目,我们可以用矩阵快速幂优化递推
时间复杂度可以达到𝑂(log𝑛)
所以只需要求
所求结果
现在我们来思考一下,如何快速的计算一个数/矩阵的n次幂:
如何快速地计算?
如果正常计算,是把𝑛个𝑎相乘,时间复杂度𝑂(𝑛)
我们来观察一下,以求𝑎的10次方为例:
这样来看的话,我们的答案是从 中的某些相乘得出的,而
可以直接从上一项相乘得出
这样的话,计算𝑛次幂的复杂度从𝑂(𝑛)变成了𝑂(𝑙𝑜𝑔𝑛)
如何快速地计算?-代码
int quick_pow(int a, int n)
{
int res=1;
while (n > 0)
{
if (n & 1){res = res * a;}
a = a * a;
n >>= 1;
}
return res;
}斐波那契数列矩阵快速幂解法代码
#include <iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int MOD = 1000000009;
struct Matrix
{
ll m [2][2];
Matrix(bool unit = false)
{
memset(m, 0, sizeof(m));
if (unit){m [0][0] = m[1][1] =1;}
}
Matrix operator*(const Matrix& other) const
{
Matrix res;
for (int i = 0; i < 2; ++i){
for (int j = 0; j < 2; ++j){
for (int k = 0; k < 2; ++k){
res.m[i][j] += m[i][k] * other.m[k][j];
res.m[i][j] %= MOD;
}
}
}
return res;
}
};
Matrix quick_pow(Matrix a, ll n)
{
Matrix res(true);
while (n > 0)
{
if (n & 1){res = res * a;}
a = a * a;
n >>= 1;
}
return res;
}
int main()
{
ll n;
cin >> n;
Matrix mat;
mat.m[0][1] = mat.m[1][0] = mat.m[1][1] = 1;
mat.m[0][0] = 0;
mat = quick_pow(mat, n - 1);
ll ans = mat.m[0][0] + mat.m[0][1];
ans %= MOD;
cout<<ans<<endl;
return 0;
}
京公网安备 11010502036488号