1. 解题思路一
这道题如果很熟悉斐波拉契数列的定义(即 f(n)=f(n-1)+f(n−2)) ,那么用递归是最易懂的方法。但是递归的时间复杂度达到O(),且空间复杂度也有O(n);所以这并不是最优解。
因此,很多朋友提到了动态规划的解法,可是很少有朋友解释为何可以用动态规划来解决?,下面,掌柜就从这里展开。
1.1 回顾动态规划
- 首先什么样的问题适合用动态规划解决?
- 符合“一个模型三个特征”的问题。
- 那么问题又来了,什么是“一个模型,三个特征”?
- “一个模型”👉:指 多阶段决策最优解模型;
- “三个特征”👉:分别是最优子结构、无后效性和重复子问题。
1.2 动态规划图解本题
先来看看它为何更适合动态规划 的方法。首先我们看这个递归树:
(为了方便解释,这里选取 f(0) 到 f(6) 进行解说)
“动态规划”的解题思路:
- 状态转移表法(回溯算法实现 - 定义状态 - 画递归树 - 找重复子问题 - 画状态转移表 - 根据递推关系填表 - 将填表过程翻译成代码);
- 状态转移方程法(找最优子结构 - 写状态转移方程 - 将状态转移方程翻译成代码)。
此题我们采用第二种思路,即状态转移方程法。
由于最优子结构上面我们已经分析了,所以直接进入写状态转移方程这一步,直接上图:
同理可以得到f(3)时的状态转移过程:
同理可以得到后面的转移过程:
所以递推的转移方程就可以得到为:a, b = b, a + b (初始a 和b 分别为 0, 1)。
3. 核心代码
# -*- coding:utf-8 -*- class Solution: def Fibonacci(self, n): # write code here #斐波拉契数的边界条件: F(0)=0 和 F(1)=1 if n < 2: return n else: a, b = 0, 1 for i in range(n-1): a, b = b, a + b #状态转移方程,每次滚动更新数组 return b
4. 复杂度分析
- 时间复杂度:O(n) (根据状态转移方程和边界条件,可以得到时间复杂度 O(n))
- 空间复杂度:O(n) (同上)
----------------------------------------我是解法二的分割线------------------------------------------------
5. 解法二
5.1 思路
由于F(n) 只和F(n−1) 与F(n−2) 有关,因此只需要初始化三个整数变量 sum, a, b ,利用辅助变量sum 使 a, b两数字交替更新即可 。
这样做就节省了动态规划列表的空间,因此空间复杂度降至 O(1)。
5.1 核心代码:
# -*- coding:utf-8 -*- class Solution: def Fibonacci(self, n): # write code here #斐波拉契数列的边界条件 if n < 2: return n #初始化三个整形变量 a, b, sum = 0, 0, 1 for i in range(2, n + 1): #利用辅助变量sum 使 a, b两数字交替更新即可 a, b = b, sum sum = a + b return sum
5.2 复杂度分析
- 时间复杂度:O(n) (根据状态转移方程和边界条件,可以得到时间复杂度 O(n))
- 空间复杂度:O(1) (见思路)