动态规划理论基础
大家知道动规是由前一个状态推导出来的,而贪心是局部直接选最优的,对于刷题来说就够用了。
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
动态规划常见的几种类型的题目:
背包问题
打家劫舍
股票问题
子序列问题
debug方法: 打印dp数组
斐波那契数 (通常用
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
class Solution(object):
def fib(self, n):
"""
:type n: int
:rtype: int
1.确定dp数组含义: dp[i]表示第i个斐波那契数的值为dp[i]
2.确定递推公式: dp[i] = dp[i-1] + dp[i-2]
3.dp数组如何初始化: dp[0] = 0, dp[1] = 1
4.确定遍历顺序: dp[i]是从dp[i-1]和dp[i-2]得到的,因此从前向后遍历
5.打印dp数组: debug
time:O(n)
space: O(1)
"""
# 排除 Corner Case
if n == 0:
return 0
# 创建 dp table
dp = [0] * (n + 1) #n是从0开始
# 初始化 dp 数组
dp[0] = 0
dp[1] = 1
# 遍历顺序: 由前向后。因为后面要用到前面的状态
for i in range(2, n + 1):
# 确定递归公式/状态转移公式
dp[i] = dp[i - 1] + dp[i - 2]
# 返回答案
return dp[n]
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
class Solution(object):
def climbStairs(self, n):
"""
:type n: int
:rtype: int
1.确实dp数组含义: dp[i]代表第阶的楼梯有dp[i]种方法
2.确定递推公式: dp[i] = dp[i-1] + dp[i-2]
3.dp数组如何初始化: dp[1] = 1,dp[2] = 2
4.确定遍历顺序:dp[i]是从dp[i-1]和dp[i-2]得到的,因此从前向后遍历
5.打印dp数组: debug
time:O(n)
space: O(1)
"""
# # 排除 Corner Case 1 <= n <= 45所以不需要考虑了
if n <= 2:
return n
# 创建 dp table
dp = [0] * (n+1)
# 初始化 dp 数组
dp[1] = 1
dp[2] = 2
# 遍历顺序: 由前向后。因为后面要用到前面的状态
for i in range(3, n + 1):
# 确定递归公式/状态转移公式
dp[i] = dp[i - 1] + dp[i - 2]
# 返回答案
return dp[n]