求解斐波那契数列的若干方法
问题链接:https://www.acwing.com/problem/content/description/207/
问题描述
方法一:递归
对于斐波那契数来说,每一项都等于他的前两项之和,他的第 0 0 0项和第 1 1 1项分别为 0 , 1 0,1 0,1,那么因为有重复的地方,所有可以使用递归来进行求解
#include <iostream>
using namespace std;
int f_final(int n) {
if(n < 2) return n;
return f_final(n-1) + f_final(n-2);
}
int main() {
int n;
cin >> n;
cout << f_final(n) << endl;
return 0;
}
递归计算的节点个数是
O
(
2
n
)
O(2^n)
O(2n)的级别的,存在大量重复计算。
时间复杂度是
O
(
2
n
)
O(2^n)
O(2n),一秒内大约能算到第四十项左右。
重复计算的原因
每一个递归的程序,都可以绘制一棵树,以表示他的执行过程
由此可以发现,递归的过程中,因为没有记忆话的能力,所有他做了很多的无用功,大量的重复计算,可以使用记忆化进行优化
方法二:记忆化搜索
可以开辟一个数组用于记录计算过的值,下一次再被搜索到的时候,就可以直接得到结果,而不需要继续递归调用了
#include <iostream>
using namespace std;
const int N = 1e6 + 10, mod = 1e9 + 7;
int f[N];
int f_final(int n) {
if(f[n]) return f[n];
if(n < 2) return f[n] = n;
return f[n] = (f_final(n-1) + f_final(n-2)) % mod;
}
int main() {
int n;
cin >> n;
cout << f_final(n) << endl;
return 0;
}
总共有 n n n 个状态,计算每个状态的复杂度是 O ( 1 ) O(1) O(1),所以时间复杂度是 O ( n ) O(n) O(n)。
一秒内可以算 n = 1 0 7 n=10^7 n=107 ,但由于是递归计算,递归层数太多会爆栈,大约只能算到 n = 1 0 5 n=10^5 n=105 级别
方法三:非递归
#include <iostream>
using namespace std;
const int N = 1e6 + 10, mod = 1e9 + 7;
int f[N];
int f_final(int n) {
f[1] = 1;
for(int i = 2; i <= n; i++)
f[i] = (f[i-1] + f[i-2]) % mod;
return f[n];
}
int main() {
int n;
cin >> n;
cout << f_final(n) << endl;
return 0;
}
总共计算
n
n
n 个状态,所以时间复杂度是
O
(
n
)
O(n)
O(n)。
但是他的瓶颈在于空间复杂度,需要开一个长度是
n
n
n 的数组,当
n
=
1
0
8
n=10^8
n=108 时,需要的内存是
4
×
1
0
8
1024
×
1024
≈
381
M
B
\frac{4\times10^8}{1024\times1024}\approx381MB
1024×10244×108≈381MB,很容易超内存
方法四:递推 + 滚动变量
从上面递推的过程中,可以发现,对于每一个状态,他只与前面两个状态有关,所有就可以对空间进行一次优化,我们不需要一直报存前面的每一个状态的结果
#include <iostream>
using namespace std;
const int mod = 1e9 + 7;
int f_final(int n) {
if(n < 2) return n;
int a = 0, b = 1, c;
for(int i = 2; i <= n; i++) {
c = (a + b) % mod;
a = b;
b = c;
}
return c;
}
int main() {
int n;
cin >> n;
cout << f_final(n) << endl;
return 0;
}
时间复杂度还是 O ( n ) O(n) O(n),空间复杂度变成了 O ( 1 ) O(1) O(1)。
方法五:快速幂 + 矩阵乘法
本题中,他的数据范围非常的大,上面的方法都会超时的,必须加以优化
对于斐波那契数列中,相邻的两项
f
n
f_n
fn和
f
n
−
1
f_{n-1}
fn−1,可以进行如下变化:
{
f
n
f
n
−
1
}
\left\{ \begin{matrix} f_n\\ f_{n-1}\\ \end{matrix} \right\}
{fnfn−1}
⇒
{
f
n
−
1
+
f
n
−
2
f
n
−
1
}
\Rightarrow \left\{ \begin{matrix} f_{n-1} + f_{n-2}\\ f_{n-1}\\ \end{matrix} \right\}
⇒{fn−1+fn−2fn−1}
⇒
{
1
×
f
n
−
1
+
1
×
f
n
−
2
1
×
f
n
−
1
+
0
×
f
n
−
2
}
\Rightarrow \left\{ \begin{matrix} 1 \times f_{n-1} + 1 \times f_{n-2}\\ 1 \times f_{n-1} + 0 \times f_{n-2}\\ \end{matrix} \right\}
⇒{1×fn−1+1×fn−21×fn−1+0×fn−2}
⇒
{
1
1
1
0
}
×
{
f
n
−
1
f
n
−
2
}
\Rightarrow \left\{ \begin{matrix} 1 & 1\\ 1 &0 \\ \end{matrix} \right\} \times \left\{ \begin{matrix} f_{n-1} \\ f_{n-2}\\ \end{matrix} \right\}
⇒{1110}×{fn−1fn−2}
所以说,想要计算第
n
n
n项,可以推导出这样一个公式
{
1
1
1
0
}
n
−
1
×
{
f
1
f
0
}
\left\{ \begin{matrix} 1 & 1\\ 1 &0 \\ \end{matrix} \right\}^{n-1} \times \left\{ \begin{matrix} f_{1} \\ f_{0}\\ \end{matrix} \right\}
{1110}n−1×{f1f0}
⇒
{
1
1
1
0
}
n
−
1
×
{
1
0
}
\Rightarrow \left\{ \begin{matrix} 1 & 1\\ 1 &0 \\ \end{matrix} \right\}^{n-1} \times \left\{ \begin{matrix} 1 \\ 0\\ \end{matrix} \right\}
⇒{1110}n−1×{10}
程序
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int MOD = 10000;
// 二维矩阵连乘
void Mul(int a[][2],int b[][2],int c[][2]) {
int t[][2] = { {0,0}, {0,0} };
for(int i = 0; i < 2; i++)
for(int j = 0; j < 2; j++)
for(int k = 0; k < 2; k++)
t[i][j] = (t[i][j] + a[i][k] * b[k][j]) % MOD;
memcpy(c,t,sizeof t);
}
int f_final(int n) {
if(n < 2) return n;
int res[][2] = { {1,0},{0,1} }; // 1
int t[][2] = { {1,1},{1,0} };
int k = n - 1;
while(k > 0) {
if(k & 1) Mul(res,t,res);
Mul(t,t,t);
k >>= 1;
}
int x[] = {1,0};
int c[] = {0,0};
for(int i = 0; i < 2; i++)
for(int j = 0; j < 2; j++)
c[i] = (c[i] + x[j] * res[j][i]) % MOD;
return c[0];
}
int main() {
int n;
while(cin >> n , n != -1) {
cout << f_final(n) << endl;
}
return 0;
}