理论基础
动态规划通常用来解决含重叠子问题的问题,动态规划中的状态可以通过前一个时刻的状态推导。解题步骤有5步:
1 确定dp数组定义和下标i的含义。 2 递推公式。 3 dp初始化。 4 递推遍历的方向。 5 举例推导
debug时打印dp数组看看与推导的是否一致,如果一致还是错了说明dp的递推公式或者初始化等条件出错了。
509. 斐波那契数
题目:斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。
示例 1:
输入: n = 2
输出: 1
解释: F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:
输入: n = 3
输出: 2
解释: F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:
输入: n = 4
输出: 3
解释: F(4) = F(3) + F(2) = 2 + 1 = 3
提示:
0 <= n <= 30
解题思路1: 自顶向下递归
使用memo记录已经算过的fib(n)从而剪枝,跳过冗余的计算。
-
时间复杂度:O(n), 没有memo的fib是O(2^n)复杂度,带有memo的fib只要计算n次。
-
空间复杂度:O(n),递归栈最大深度是n,memo也是n,所以是O(n)。
class Solution:
memo = {0:0, 1:1} # dp[n]对应的值
def fib(self, n: int) -> int:
if n < 2:
return n
if n not in self.memo:
res = self.fib(n-1) + self.fib(n-2)
self.memo[n] = res
return self.memo[n]
解题思路2: 自底向上递推(动态规划)
递推:
1 dp数组定义和下标i的含义: dp[i]表示第i个斐波那契数 size=n+1
2 递推公式:dp[i]=dp[i-1]+dp[i-2]
3 dp初始化: dp[0] = 0 dp[1]=1
4 递推遍历的方向: 从前到后
5 举例推导: n=5 0 1 1 2 3 5
-
时间复杂度:O(n),递推n次。
-
空间复杂度:O(n), dp数组大小。
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
# 初始化
dp = [0] * (n+1)
dp[0] = 0
dp[1] = 1
# 递推
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
解题思路3: 动态规划 状态压缩
由于dp[i]仅仅依赖于dp[i-1]和dp[i-2],可以使用pre和cur替代两个状态,删除原先O(n)的dp数组空间开销。
-
时间复杂度:O(n),
-
空间复杂度:O(1)。
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
# 初始化
pre = 0
cur = 1
# 递推
for i in range(2, n+1):
val = pre + cur
pre = cur
cur = val
return cur
总结
70. 爬楼梯
题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入: n = 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: n = 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
提示:
1 <= n <= 45
解题思路1: 自顶向下递归
本题和斐波那契类似,达到climbStairs(n)有两种方法,第一种是从n-1走1步,第二种是从n-2走2步,得到递推关系:dp[i] = dp[i-2] + dp[i-1], 注意dp[0]=1。
使用memo记录已经算过的climbStairs(n)从而剪枝,跳过冗余的计算。
-
时间复杂度:O(n), 没有memo的climbStairs是O(2^n)复杂度,带有memo的climbStairs只要计算n次。
-
空间复杂度:O(n),递归栈最大深度是n,memo也是n,所以是O(n)。
class Solution:
memo = {0:1, 1:1}
def climbStairs(self, n: int) -> int:
if n < 2:
return self.memo[n]
if n not in self.memo:
res = self.climbStairs(n-1) + self.climbStairs(n-2)
self.memo[n] = res
return self.memo[n]
解题思路2: 自底向上递推(动态规划)
递推:
1 dp数组定义和下标i的含义: dp[i]表示爬到第i层楼需要的步数 size=n+1
2 递推公式:dp[i]= dp[i-2] + dp[i-1] # 到dp[i]时有两种走法:走1步或者走两步,对应dp[i-1]次数走1步,和dp[i-2]走两步
3 dp初始化:
dp[0] = 1
dp[1] = 1
dp[2]= 2 # 走2x1 1x2
4 递推遍历的方向: 从前到后
5 举例推导: n=5
dp[0] = 1
dp[1] = 1
dp[2]= 2
dp[3] = 3
dp[4] = 5
dp[5] = 8
-
时间复杂度:O(n),递推n次。
-
空间复杂度:O(n), dp数组大小。
class Solution:
def climbStairs(self, n: int) -> int:
# 初始化
dp = [0] * (n+1)
dp[0] = dp[1] = 1
if n < 2:
return dp[n]
# 递推
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2] # 走1步或2步
return dp[n]
解题思路3: 动态规划 状态压缩
由于dp[i]仅仅依赖于dp[i-1]和dp[i-2],可以使用pre和cur替代两个状态,删除原先O(n)的dp数组空间开销。
-
时间复杂度:O(n),
-
空间复杂度:O(1)。
class Solution:
def climbStairs(self, n: int) -> int:
# 初始化
pre = 1
cur = 1
# 递推
for i in range(2, n+1):
val = pre + cur
pre = cur
cur = val
return cur
总结
57. 爬楼梯(第八期模拟笔试)
题目:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
输入描述:
输入共一行,包含两个正整数,分别表示n, m
输出描述:
输出一个整数,表示爬到楼顶的方法数。
输入示例:
3 2
输出示例:
3
提示信息:
数据范围:
1 <= m < n <= 32;
当 m = 2,n = 3 时,n = 3 这表示一共有三个台阶,m = 2 代表你每次可以爬一个台阶或者两个台阶。
此时你有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶段
- 1 阶 + 2 阶
- 2 阶 + 1 阶
解题思路
dp[i] = dp[i-1] + dp[i-2] + ... + dp[i-m]
-
时间复杂度:O(n),
-
空间复杂度:O(n),。
import sys
def climb_stairs(n, m):
# 创建一个数组来存储到达每一阶的方法数
dp = [0] * (n + 1)
dp[0]=1 # 到达第0阶的方法数为1
# 计算每一阶的方法数
for i in range(1, n+1):
for j in range(1, min(m,i)+1):
dp[i] += dp[i-j]
return dp[n]
# 输入
n, m = map(int, sys.stdin.readline().split())
# 输出结果
print(climb_stairs(n, m))
总结
746. 使用最小花费爬楼梯
题目:给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
示例 1:
输入: cost = [10,15,20]
输出: 15
解释: 你将从下标为 1 的台阶开始。
- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15 。
示例 2:
输入: cost = [1,100,1,1,1,100,1,1,100,1]
输出: 6
解释: 你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。
提示:
2 <= cost.length <= 10000 <= cost[i] <= 999
解题思路
dp[i]表示达到第i个台阶花费的体力值cost
注意:
1 第i个台阶的cost[i]暂时不计入,只有跨过i时候才要dp[i] + cost[i]。
2 第0和1个台阶不算cost。
递推:
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2]+cost[i-2])
-
时间复杂度:O(n)。
-
空间复杂度:O(n)。
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
n = len(cost)
# 初始化
dp = [0] * (n+1)
dp[0], dp[1] = 0, 0
for i in range(2, n+1):
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2]+cost[i-2])
return dp[n]