剑指 Offer 10-I-斐波那契数列

题目

思路

解法一

看到斐波那契数列,最容易想到的解法就是递归。这道题用递归解起来也非常简单

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)级别,整体上要优于记忆化搜索。

解法二

矩阵快速幂解法,参考这篇文章(传送门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值