斐波那契数列 即 1、1、2、3、5、8、13、21、34、.....以此类推,在很多面试题中,面试官都会让你手写斐波那契数列的实现。对于一些有编程经验的人来说,这很容易,他们可以很快写出类似以下代码:
设 n 为 大于0的正整数,求第n个斐波那契数(1为第一个,2为第二个...8为第五个)
def feb(n):
if n == 1 or n == 2:
return n
else:
return feb(n - 1) + feb(n - 2)
#这里设数列从 1,2,3,5,8 开始
但是这里有个很严重的问题就是重复计算
例如,在计算feb(5) 时,feb(1) 会调用多少次?feb(2)呢?
运行结果可见下图
可以看到 feb(2)feb(1) 被多次调用
如何能避免这个问题呢?
避免重复 的关键在于 要实现检查即将进行的计算是否已经经历过。
有同学会想到使用列表,每计算一个feb(n),就将结果存储到列表的下标n 处。
result=[1,,1,2,3,5,8.....]
feb(n)=result[n]
这看起来似乎是一个好方法,但因为斐波那契数列可以是无限长的,如果计算feb(10000)是否真的需要长度10000的列表?(况且在python中实际列表所占地址空间会大于其可见长度。)所以这种方式显然不是可取的方式。
但是在斐波那契数列数列需要经常进行运算且n较小的时候,直接采取已经定义好的列表看起来的确是个一劳永逸的方法。
但是如果面试官要求你不使用列表,即尽可能减少内存占用呢?
这里问题就可以简化为只使用两个变量。本身斐波那契数列 中 第n个就是 前两个数相加的和。
只需要一直更新 feb(n-1) 和 feb(n-2) 的值即可。
现在我们可以使用for循环来实现:
def feb(n):
if n == 1 or n == 2:
return n
else:
n1,n2=1,2
for i in range(3, n):
n1,n2=n2,n1+n2
return n1 + n2
这样做已经效果很好了,不需要借助数组等大存储 对象,时间复杂度也只是O(n)级别。
但是有没有更好的解法?
学习高等数学、线性代数等课程时,可能有数学老师提到过斐波那契数列的另类解法--利用矩阵求解。
数列的递推公式为:f(1)=1,f(2)=2,f(n)=f(n-1)+f(n-2)(n>=3)
用矩阵表示为:
进一步,可以得出直接推导公式:
接下来,我们先思考,如何用python实现矩阵相乘?
如果接触过numpy 库的话,你也许会想到numpy中的 dot 方法。
利用这个方法可以直接计算两个矩阵(列表)的乘积。但是如果让你自己去实现呢?
先提醒大家,矩阵可以相乘是有前提的
设有矩阵A*B
矩阵只有当左边矩阵A的列数等于右边矩阵B的行数时,才可以相乘。乘积结果矩阵的行数等于左边矩阵的行数,乘积矩阵的列数等于右边矩阵的列数
矩阵的乘法是左行乘右列
def matrixMul(A, B):
res = [[0] * len(B[0]) for i in range(len(A))] #结果变量
for i in range(len(A)): #对于A的每行
for j in range(len(B[0])): #对于B的每列
for k in range(len(B)): #对于B的每行
res[i][j] += A[i][k] * B[k][j]
return res
上面的代码是可用的,但是还有什么可以优化的地方吗?
注意到循环的层数足足三层,每一层都需要计算 len()。
所以最完美的写法是应该是先计算好长度再直接用长度值去遍历。
def matrixMul(A, B):
line_A,line_B,column_B=len(A),len(B),len(B[0])
res = [[0] * column_B for i in range(line_A)] #结果变量
for i in range(line_A): #对于A的每行
for j in range(column_B): #对于B的每列
for k in range(line_B): #对于B的每行
res[i][j] += A[i][k] * B[k][j]
return res
现在,我们可以用 矩阵乘法来 实现 斐波那契数列了
def matrixMul(A, B):
line_A,line_B,column_B=len(A),len(B),len(B[0])
res = [[0] * column_B for i in range(line_A)] #结果变量
for i in range(line_A): #对于A的每行
for j in range(column_B): #对于B的每列
for k in range(line_B): #对于B的每行
res[i][j] += A[i][k] * B[k][j]
return res
def feb(n):
if n <= 1: return max(n, 0)
res = [[1, 1], [0, 1]]
# 单位矩阵,等价于1 。如果 res 取值为[[1,0],[0,1]] 则会得1,1,2,3,5,8...这个无所谓
A = [[1, 1], [1, 0]] # A矩阵
while n:
if n & 1: res = matrixMul(res, A) # 如果n是奇数,或者直到n=1停止条件
A = matrixMul(A, A) # 快速幂
n >>= 1 # 整除2,向下取整
return res[0][1]