【LetMeFly】尝试以四种方式吃透:509.斐波那契数(四种大方法+两种小优化)
先说明本题解法:
- 动态规划(及 原地滚动的优化)
- 递归(及 记忆化的优化)
- 矩阵快速幂
- 通项公式
力扣题目链接:leetcode.cn/problems/fi…
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。
示例 1:
输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:
输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:
输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3
提示:
方法一:动态规划
开辟一个大小为30的数组a(或开辟大小为n+1的数组也可)
初始值a[0]=0,a[1]=1
之后从下标2开始到n为止,使用转移方程a[n]=a[n−1]+a[n−2]求解
最终返回a[n]即可
- 时间复杂度O(n)
- 空间复杂度O(C),这里C是数据中n的最大值30(也可以只开辟n+1的数组,则空间复杂度为O(n))
AC代码
C++
class Solution {
public:
int fib(int n) {
int a[31] = {0, 1};
for (int i = 2; i <= n; i++)
a[i] = a[i - 1] + a[i - 2];
return a[n];
}
};
方法一.2:动态规划 + 原地滚动
不难发现,在计算a[n]时,我们只用到了a[n−1]和a[n−2],再往前的数据就再也用不到了
因此,我们只需要使用两个额外的空间0和1来分别记录a[n−1]和a[n-2]的值,在计算过程中,不断更新_0和_1$的值即可
- 时间复杂度O(n)
- 空间复杂度O(1)
AC代码
C++
class Solution {
public:
int fib(int n) {
if (n < 2)
return n;
int _0 = 0, _1 = 1;
int ans;
for (int i = 2; i <= n; i++) {
ans = _0 + _1;
_0 = _1, _1 = ans;
}
return ans;
}
};
方法二:递归
斐波那契数列很容易看出“递归”
题目都说明了,F(n)=F(n−1)+F(n−2),终止条件是n=0或n=1
那么,我们只需要在非终止条件下无脑递归即可。
- 时间复杂度O(n2),这个时间复杂度待会儿分析
- 空间复杂度O(n),不论总递归次数为多少,总递归深度为n
AC代码
C++
class Solution {
public:
int fib(int n) {
if (n < 2)
return n;
return fib(n - 1) + fib(n - 2);
}
};
方法二.2:递归 + 记忆化
方法二在数据量小的前提下能很轻松地计算出结果。
但是为什么方法二的时间复杂度是O(n2)呢?
不难发现,计算F(5)时,会调用F(4)和F(3),但在计算F(4)时,会再调用一次F(3),也就是说F(3)不只被调用了一次
例如计算F(6)时:

F(4)计算了两次,F(3)计算了三次,F(2)更是计算了五次。
n越大,这种重复计算就越明显。
那么,既然算过一遍F(3)了,为什么要再算一次呢?
记忆化出现了
我们使用一个哈希表,将计算过的结果记录下来,那么再次调用这个函数时,直接返回之前计算过的结果不就可以了吗?

这样就避免了没必要的重复计算。
- 时间复杂度O(n)
- 空间复杂度O(n)
AC代码
C++
class Solution {
private:
unordered_map<int, int> ma;
public:
int fib(int n) {
if (n < 2)
return n;
if (ma.count(n))
return ma[n];
return ma[n] = fib(n - 1) + fib(n - 2);
}
};
方法三:矩阵快速幂
[1110][anan−1]=[an+an−1an]=[an+1an]
因此
[an+1an]=[1110]n[a1a0]
因此可以使用矩阵快速幂求出
[1110]n−1
再将其右乘[a1a0]即得到[anan−1]
假设:
[1110]n−1=[xmyn]
那么:
[anan−1]=[1110]n−1[a1a0]=[xmyn][10]=[xm]
因此an=x,也就是[1110]n−1的左上角的值
- 时间复杂度O(logn)
- 空间复杂度O(1)
AC代码
C++
struct Matrix {
int a[2][2];
Matrix(int x, int y, int m, int n) {
a[0][0] = x, a[0][1] = y;
a[1][0] = m, a[1][1] = n;
}
Matrix() {
a[0][0] = 0, a[0][1] = 0;
a[1][0] = 0, a[1][1] = 0;
}
};
Matrix operator* (const Matrix& a, const Matrix& b) {
Matrix result;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
result.a[i][j] = a.a[i][0] * b.a[0][j] + a.a[i][1] * b.a[1][j];
}
}
return result;
}
class Solution {
private:
Matrix Pow(Matrix a, int n) {
Matrix result(1, 0, 0, 1);
while (n) {
if (n & 1)
result = result * a;
n >>= 1;
a = a * a;
}
return result;
}
public:
int fib(int n) {
if (n < 2)
return n;
return Pow(Matrix(1, 1, 1, 0), n - 1).a[0][0];
}
};
方法四:通项公式
使用生成函数求斐波那契的通项公式
设 F(x)=F1x+F2x2+F3x3+F4x4+⋯
则 xF(x)= F1x2+F2x3+F3x4+⋯
且x2F(x)= F1x3+F2x4+⋯
因此,(x2+x)F(x)=F1x2+(F2+F1)x3+(F3+F4)x4+⋯=F1x2+F3x3+F4x4+⋯
因此,(1−(x2+x))F(x)=F1x+(F2−F1)x2
即(1−x−x2)F(x)=x
所以F(x)=1−x−x2x=−(x−25−1)(x+25+1)x=−51(25−1x+11+25+1x−11)
我们求出了斐波那契数列的生成函数
接下来通过生成函数求原始数列
设G(x)=ax+b1,其对应的原始数列是{gi}
则有ax+b1=∑i=1∞gixi
已知n→∞时1+x+x2+⋯+xn=1−x1
用−x替换x得1+x1=1−x+x2−x3+⋯
所以bx+b1=b1−b1x+b1x2−b1x3+⋯
用bax替换x得ax+b1=b1−b2ax+b3a2x2−b4a3x3+⋯
所以xn的系数gn=(−1)nanb−(n+1)
之前我们求出F(x)=−51(25−1x+11+25+1x−11)
对于25−1x+11,令a=25−1,b=1得gn=(21−5)n,因此G(x)={(21−5)x}
对于25+1x−11,令a=25+1,b=−1得gn=−(21+5)n,因此G(x)={−(21+5)x}
所以an=−51[(21−5)x−(21+5)x]=51[(21+5)n−(21−5)n] 即为所求
- 时间复杂度不好衡量(也不能说O(1)吧,但是肯定比矩阵快速幂快,毕竟CPU有专门的求幂指令)
- 空间复杂度O(1)
AC代码
C++
class Solution {
public:
int fib(int n) {
return 1. / sqrt(5) * (pow((1 + sqrt(5)) / 2, n) - pow((1 - sqrt(5)) / 2, n));
}
};
有同学说,比赛的时候忘记了通项公式怎么办,总不能现场手推一遍吧
其实我们只需要记得通项公式的大概形势:an=51[(21+5)n−(21−5)n]=x1y1n+x2y2n
因此代入几个a就把x1,y1,x2,y2解出来了(a0=0,a1=1,⋯)
同步发文于CSDN,原创不易,转载请附上原文链接哦~
Tisfy:letmefly.blog.csdn.net/article/det…