题目
思路
解法一
看到斐波那契数列,最容易想到的解法就是递归。这道题用递归解起来也非常简单
private const int MOD = 1000000007;
public int Fib(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return (Fib(n - 1) + Fib(n - 2)) % MOD;
}
但是注意n的范围是[0,100]
,而采用递归方式的时间复杂度是
O
(
2
n
)
O(2^n)
O(2n)级别。随着n的增大,消耗的时间会爆炸性的增长。可以试一下输入n=100,我的电脑跑了5分钟都没有跑完。。。
既然这套解法必定会超时,我们就来分析一下有没有可以优化的点。以n=5为例,画一下函数调用的过程
可以看到,上面的递归过程其实有很多都是重复求解。那么优化的思路就很明确了:用一个额外的存储结构,将已经求解过的结果储存起来。也就是暴力递归->记忆化搜索
private const int MOD = 1000000007;
private Dictionary<int, int> _resultMap = new();
public int Fib2(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
if (_resultMap.ContainsKey(n))
return _resultMap[n];
int res = (Fib2(n - 1) + Fib2(n - 2)) % MOD;
_resultMap[n] = res;
return res;
}
经过优化后的函数调用过程如下
时间复杂度来到了
O
(
n
)
O(n)
O(n)级别。粗略统计运行耗时也可以明显看出时间消耗的减少
既然已经成功实现了记忆化搜索,那么我们也可以着手尝试将其改造成严格表结构的动态规划。上面使用的哈希表可以看做一个一维数组,根据动态规划自底向上的原则(暴力递归是自顶向下),可以先列出已知条件和要求解的目标
根据前面的经验,我们知道要想求出
F
(
5
)
F(5)
F(5)就要先求出
F
(
2
)
F(2)
F(2),而
F
(
2
)
F(2)
F(2)根据
F
(
1
)
+
F
(
0
)
F(1)+F(0)
F(1)+F(0)很容易求得
同理,
F
(
3
)
F(3)
F(3)也可以由
F
(
2
)
+
F
(
1
)
F(2)+F(1)
F(2)+F(1)求得
以此类推…
也就是说,我们可以根据这个规律,直接从
F
(
0
)
+
F
(
1
)
F(0)+F(1)
F(0)+F(1)开始算起,直到算出目标值。这样就只需要有限几个变量进行存储,也不会进行重复计算
private const int MOD = 1000000007;
public int Fib3(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
// f(0) f(1)
int left = 0,right = 1;
int res = 0;
for (int i = 2; i <= n; i++)
{
res = (left + right)%MOD;
left = right;
right = res;
}
return res;
}
动态规划解法的时间复杂度与记忆化搜索的时间复杂度相同,都为 O ( n ) O(n) O(n)。但是前者的空间复杂度由 O ( n ) O(n) O(n)降低到了 O ( 1 ) O(1) O(1)级别,整体上要优于记忆化搜索。
解法二
矩阵快速幂解法,参考这篇文章(传送门)