斐波那契额序列基本上是算法入门绕不开的题。就好像有人想养成看书的习惯,我可能推荐他看的第一本书那就是《解忧杂货店》。斐波那些序列这道题,不但实现方法多样,而且更是提前让像我这样入门的小白,提前体会到了记忆化
和DP动态规划
的入门思想。这这样从易到难,从懵懂到大彻大悟的学习方法是有效果的。
题目要求如下:
现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)
递归简直就是为斐波那些序列的计算而设计的,所以最最最最朴素,最最最最直观,最最最简单的方式就是用递归法。而且简单粗暴非常就两句话就可以实现。废话不多说直接上代码。
def Fibonacci(n)
if n <= 1: return n
return Fibonacci(n - 1) + Fibonacci(n - 2)
图1 斐波那些序列递归实现
递归的实现方式算是暴力法的一种,因为它的时间复杂度是O(2n)的,这里时间复杂度的结果并不需要用主定理,而是这个递归就是一个二叉树,每层会有两个分支点。
其实有点接近动态规划的方法了,动态规划其实就是遍历+记忆化的方式。对图1的仔细观察可以得知,递归法进行了很多的重复计算。那么我们优化的方向也就有了,就是通过记忆化来,减少重复的计算。开辟一个数组nums = [0] * (n + 1),一旦计算过某个数值就将它保存下来。每次在进行分支之前检查下是否该数值已经被计算过了。
class Solution:
def Fibonacci(self, n):
def Fibonacci_m(n, memo):
if n <= 1: return n
if not memo[n]:
memo[n] = Fibonacci_m(n - 1, memo) \
+ Fibonacci_m(n - 2, memo)
return memo[n]
memo = [0] * (n + 1)
return Fibonacci_m(n, memo)
这样每个数值只会被计算一次,也就起到了优化算法的作用。那么还有没有更快的算法?
丧心病狂?这还不够吗?还想再快点?额…其实很多算法题是有很多反逻辑反人类的想法的,我觉得在面试过程里问这些歪门邪道的优化真的都不是正常逻辑想出来的,除非你是数学家,整天研究这些。所以就当做下面这个O(logn)的算法是从天上掉下来的吧。
下面我们看一下斐波那些序列的推导公式:
对公式进行一下矩阵转化:
[ F ( n ) F ( n − 1 ) ] = [ 1 , 1 1 , 0 ] [ F ( n − 1 ) F ( n − 2 ) ] = [ 1 , 1 1 , 0 ] n − 1 [ F ( 1 ) F ( 0 ) ] \begin{bmatrix} F(n) \\ F(n - 1) \end{bmatrix} =\begin{bmatrix} 1, 1\\ 1, 0 \end{bmatrix} \begin{bmatrix} F(n - 1) \\ F(n - 2) \end{bmatrix} =\begin{bmatrix} 1,1 \\ 1,0 \end{bmatrix}^{n-1} \begin {bmatrix} F(1) \\ F(0) \end{bmatrix} [F(n)F(n−1)]=[1,11,0][F(n−1)F(n−2)]=[1,11,0]n−1[F(1)F(0)]
另 M = [ 1 , 1 1 , 0 ] n − 1 M=\begin{bmatrix} 1,1 \\ 1,0 \end{bmatrix}^{n-1} M=[1,11,0]n−1其实就等于计算:
p o w ( M , n − 1 ) ∗ [ 1 1 ] = [ F ( n ) F ( n − 1 ) ] pow(M, n - 1) * \begin{bmatrix}1\\1\end{bmatrix}=\begin{bmatrix}F(n) \\ F(n - 1) \end{bmatrix} pow(M,n−1)∗[11]=[F(n)F(n−1)]
其中涉及到矩阵乘法的计算,由于Python从不重复造轮子,于是实现代码我引入了np数值计算包,具体算法实现如下:
def Fibonacci(n):
M = np.array([[1,1], [1, 0]])
r = np.linalg.matrix_power(M, n - 1).dot([[1], [1]])
return r[0]