题目描述
斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。
Leetcode 509: https://leetcode-cn.com/problems/fibonacci-number/.
解题思路
1. 暴力递归
def fib(self, N: int) -> int:
if N ==1 or N ==2 :
return 1
return fib(N-1)+fib(N-2)
结论:这个方法十分低效,时间复杂度为O(2**n),存在大量重复计算的过程比如:
f(20) = f(18) + f(19) # 需要计算f(18)
f(19) = f(18) + f(17) # 还是需要计算f(18)
递归的时间复杂度如何求解?:
子问题个数*解决一个子问题需要的时间复杂度。该题的子问题个数为递归树中的节点总数,子问题的时间复杂度为O(1)
2. 重叠子问题求解
从上述分析,暴力递归存在大量的重复计算,这样的问题就是重叠子问题。因为有重复计算所以可以写一个列表或者字典,将以前计算过的存下来。
def fib(self, N: int) -> int:
def help(memo,n):
if n == 1 or n == 2:
return 1
if memo[n] != 0:
return memo[n]
memo[n] = help(memo,n-1)+help(memo,n-2)
return memo[n]
if N<1:
return 0
memo = [0 for i in range(N+1)]
return help(memo,N)
总结:这样就把很多重复计算少了很多,极大得减少了子问题。这样叫做自顶向下的方式。时间复杂度为 O(n)
3. 动态规划
除了自顶向下,也可以采用自底向上的方式。先算f(1),f(2)… 慢慢算到f(N)。
def fib(self, N: int) -> int:
if N == 1 or N==2:
return 1
if N ==0 :
return 0
pre = 1
pre2 = 1
for i in range(3,N+1):
cur = pre+pre2
pre = pre2
pre2 = cur
return cur
总结: 此时的状态转移方程为:dp[i] = dp[i-1] + dp[i-2]
.时间复杂度O(n), 空间复杂度O(1)。
动态规划的三要素:
- 重叠子问题
- 最优子结构
- 状态转移矩阵
动态规划问题的一般形式就是求最值,求解动态规划的核心就是穷举。因为要求最值。但是动态规划的最值有点特殊,因为这类问题存在重叠子问题,如果暴力穷举效率会比较低。所以需要存储来优化结构。动态规划一定具备最优子结构,这样才能通过子问题的最值得到原问题的最值。
但是fib中并没有最优子结构,严格来说fib并不是严格的动态贵规划问题,没的最值。
references
[1] Leetcode 509: https://leetcode-cn.com/problems/fibonacci-number/.
[2] labuladong的算法小抄: https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-xiang-jie-jin-jie