序言

这篇文章在 notion 那边放了很久了,现在拉过来这边重新复刻一遍,其实只是为了试试博客的 mathjax 没有正常工作()

记得那个时候写这篇文章的时候,大家在出面试题,这里就提到了青蛙跳格子的经典问题(就是 斐波那契数列 求 n 项的变种),所以顺手就整理了一篇文章出来。

解法

朴素递归求解方式

快速脑补法算时间复杂度 (大雾)

易得该求解方式可以构造为二叉树,但不是一个满二叉树,其运算次数约等于树上的节点,而满二叉树的节点总数为:

所以 时间复杂度至少为 但此解应当不是一个足够小的逼近上界,毕竟我可以胡扯他的 时间复杂度,你也不能说我是错的,毕竟结果在这个区间里面。

废话文学

话是这么说没错,但是按这个理论岂不是所有 算法 复杂度都可以表示

当然我们易证此解大小应当小于 且数量级大致相差不大,那么我们讨巧的可以构造如下方程式求解(因为时间复杂度 的求解上,我们可以近似认为下述式子是相等的):

显然为单调递减,不符合结果,所以 时间复杂度可以估算为

数学证明方式

那么如果需要精确求解时间复杂度,可不能 “大概”、“脑补”、“易得”了,我们得通过数学方法分析(如果只用高中知识的话可以凑出一个等比数列求解——类似于上面的快速脑补法)。

首先朴素递归方式可以这样计算 时间复杂度

调用的时间为 那么必有:

因为 可以忽略,那么只需要求出 即可

那么这个式子本身就是一个斐波那契数列,而斐波那契数列是一个二阶常系数齐次线性递推数列:

故该齐次递推数列有如下特征方程:

代入 可求解出上述方程式的解为 :

代入通项公式:

代入后转换为大O(其实直接求级数后去掉常数项就可以了):

尾递归/迭代法

我们可以清楚的发现上面那个求解方法有很多重复项,而且编程中常见的有尾递归(就是 goto)的优化策略。而任何尾递归写法基本都可以转化为迭代法求解方法。比如可以这么写:

if (n < 1) { 
	return 0 
} 
if (n < 2) { 
	return 1
} 
let a1 = 1;
let a2 = 1; 
let sum = 1; 
for(let i = 3; i < n; i++) {
	sum = a1 + a2; 
	a1 = a2; 
	a2 = sum 
} 
return sum

所以不用算:

通项公式解法

上文我们已经求解出了通项公式,那么易得:

很多材料上都会说这玩意儿是 复杂度,用屁股想想都知道不可能,你看上面有两个大大 n 次方。而且通项公式还容易出现精度丢失,出现 浮点数 的问题(当然你可以强行转换为整数 ,但是迭代情况下精度累计丢失可能使得结果不正确)

首先求指数最简单的方法是迭代求解,都不用写代码,易得 时间复杂度 是:

当然求指数最快的方法是矩阵求解方法,俗称快速幂。

比如求解 , 假定求 ,我们可以转化为二进制求法 ,直接拼成:

那么我们往上代换以下马上可以得到通项公式:

显然 n 有 个二进制位,所以假定我们知道 ,那么只需要做 次乘法以后就可以快速获得结果。

大家是不是发现问题了,显然所在位很好求,所以其实只需要求出 的关系就可以,那么关系好求么?那可太好求了,直接拿上一项平方一下就可以拿到了,注意一下是不是和之前的迭代法就比较像了?

那么这个算法的总体复杂度会是多少呢?

我们首先花了的时间求了第一项,随后花了 的时间相乘,那么总体 时间复杂度就是:

本文标题:斐波那契数列算法时间复杂度

永久链接:https://iceprosurface.com/2022/fibonacci/

作者授权:本文由 icepro 原创编译并授权刊载发布。

版权声明:本文使用「署名-非商业性使用-相同方式共享 4.0 国际」创作共享协议,转载或使用请遵守署名协议。

查看源码: