理论基础
动态规划五部曲:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
509. 斐波那契数
思路
- 确定dp数组以及下标的含义:
dp[i]
的定义为:第i个数的斐波那契数值是dp[i]
- 递推公式:
dp[i] = dp[i - 1] + dp[i - 2]
; - 初始化:
dp[0] = 0, dp[1] = 1
- 遍历顺序:从前向后,因为dp后面的元素依赖于前面的元素
return dp[n]
代码
最简单的递归
class Solution:
def fib(self, n: int) -> int:
if n == 0:
return 0
if n == 1:
return 1
return self.fib(n-1) + self.fib(n-2)
复杂度
- 时间复杂度:
O(2^n)
- 空间复杂度:
O(n)
,算上了编程语言中实现递归的系统栈所占空间
动态规划一
class Solution:
def fib(self, n: int) -> int:
dp = [0] * (n+1)
dp[0] = 0
if n > 0:
dp[1] = 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
复杂度
- 时间复杂度:
O(n)
- 空间复杂度:
O(n)
动态规划二——优化
其实我们只需要维护两个值就可以了
class Solution:
def fib(self, n: int) -> int:
one = 1
two = 0
for i in range(2, n+1):
temp = one + two
one, two = temp, one
return one if n != 0 else two
复杂度
- 时间复杂度:
O(n)
- 空间复杂度:
O(1)
70. 爬楼梯
思路
爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。
爬到第三次楼梯只能有以下两种形式:
- 从第二层楼梯往上爬一层
- 从第一层楼梯往上爬两层
也就是说爬到第三层楼梯的方法总和等于爬到第一层的方法 + 爬到第二层的方法
要点
需要区分的是,本题求的是怕上楼层的方法总数,而不是步数,所以不要dp[i] = 1 + dp[i-1]
。
正确的状态转移方程应该是dp[i] = dp[i-1] + dp[i-2]
。
到这里应该发现这一题和斐波那契数列是一个模子出来的。
代码
只展示dp数组的写法
# 空间复杂度为O(n)版本
class Solution:
def climbStairs(self, n: int) -> int:
if n <= 1:
return n
dp = [0] * (n + 1)
dp[1] = 1
dp[2] = 2
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
746. 使用最小花费爬楼梯
思路
dp[i]
: 到达第i台阶所花费的最少体力为dp[i]
。dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
- 初始化:
dp = [0] * (len(cost)+1)
- 遍历顺序:从index2开始从前往后遍历。
- 因为这个题要求是到楼顶,所以实际上求的是
dp[len(cost)]
的值
代码
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
dp = [0] * (len(cost)+1)
for i in range(2, len(dp)):
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
return dp[-1]
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
因为只需要记住dp[i]前的两个元素,所以dp是可以优化的。只用两个variable记录即可。