斐波那契数列1(Fibonacci sequence),又称黄金分割数列、因数学家莱昂纳多·斐波那契(Leonardoda
Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……
先来看看用递归的方法是怎么写的
def f(n: int) -> int:
if n == 0:
return 0
elif n <= 2:
return 1
arr = [0] * (n+1)
def helper(meno, n):
if n == 1 or n == 2:
meno[n] = 1
return meno[n]
if arr[n] != 0: # 优化,在递归之前先去数组查看,如果已经递归过了直接拿出来用
return arr[n]
meno[n] = helper(arr, n-1) + helper(arr, n-2)
return arr[n]
return helper(arr, n)
为什么加上了个伪字,这是因为对于斐波拉契数列来说,并没有涉及到动态规划求最优解(最值)的过程,而是用到了动态规划的解题思维
我们知道,斐波那契数列的原理当前值为前两个值之和,而初始值为0和1
那么,问题的 状态转移方程 就出来了,即描述问题结构的数学公式
def fib(n: int) -> int:
if n == 0:
return 0
elif n <= 2:
return 1
n = n+1 # 再计算斐波那契数列时存在初始值0,得对n进行+1操作
dp = [0] * n # 定义DP Table
for i in range(n):
if i in (0, 1): # 对前两个值初始化
dp[i] = i
else:
dp[i] = dp[i-1] + dp[i-2] # 上一个值与上两个值之和即为下一个值
return dp[-1]
将上面的代码进行简化,可以这么去写
def fib(n: int) -> int:
n += 1 # 再计算斐波那契数列时存在初始值0,得对n进行+1操作
dp = [0] * n
dp[:2] = [0, 1] # 对前两个值初始化
for i in range(2, n):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n - 1]
此方法是根据上面所使用到的伪动态规划方法优化而来,根据 斐波那契数列 转移方程可知,当前值与前两个值有关,那么只要想办法存储前两个值就可以进一步优化
def fib(n: int) -> int:
if n == 0:
return 0
prev, curr = 0, 1 # 定义初始值
for _ in range(n-1):
sum_ = prev + curr # 计算前两个值之和
prev = curr # 当前的值变成上一个值
curr = sum_ # 更新当前值
return curr # 返回当前值
在python中,可以实现同时赋值,其写法如下
a, b = 1, 2
根据这个特性,再去改写一下代码
def fib(n: int) -> int:
if n == 0:
return 0
prev, curr = 0, 1
for _ in range(n-1):
prev, curr = curr, prev+curr # 同时进行赋值
return curr # 返回当前值
在Python中,我们成功从最初的 伪动态规划 需要新建一维数组的操作,优化到上面的代码
如果即想 根据存储前两个状态解析,又想存储斐波那契数列 该怎么做?
很简单,新建一个一维数组,并且在获得当前值时存储进去即可
def fib(n: int) -> int:
if n == 0:
return 0
prev, curr = 0, 1
f_arr = [prev, curr] # 新建数组,并且将初始值放入
for _ in range(n-1):
prev, curr = curr, prev+curr # 同时进行赋值
f_arr.append(curr) # 添加新的当前值
return curr # 返回当前值
在第一次编写的时候(大概是大半年前),我还是个算法小白,在学习途中一直模仿着别人的解法写下笔记,如今回过头来,我逐渐明白为什么要去怎么做,看着第一次写下的笔记发现有着许多不足,于是我第二次修改编写,在标题上加个2.0
斐波纳契数列 维基百科 ↩︎