带你学会动态规划
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) (同上)
例题1.跳台阶
描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
数据范围:1≤n≤40
要求:时间复杂度:O(n) ,空间复杂度:O(1)
输入:2
返回值:2
说明:青蛙要跳上两级台阶有两种跳法,分别是:先跳一级,再跳一级或者直接跳两级。因此答案为2
class Solution:
def jumpFloor(self , number: int) -> int:
# write code here
if number<2:
return number
res=0
a=1
b=1
for i in range(2,number+1):
res=(a+b)
a,b=b,res
return res
例题2.跳台阶
输入:[1,100,1,1,1,90,1,1,80,1]
返回值:6
说明:
你将从下标为 0 的台阶开始。
1.支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
2.支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
3.支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
4.支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
5.支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
6.支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。
class Solution:
def minCostClimbingStairs(self , cost: List[int]) -> int:
# write code here
#dp[i]表示爬到第i阶楼梯需要的最小压力
dp=[0 for i in range(len(cost)+1)]
for i in range(2,len(cost)+1):
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
return dp[len(cost)]
例题3.不同路径的数目(一)
输入:2,2 返回值:2
class Solution:
def uniquePaths(self , m: int, n: int) -> int:
# write code here
# if m==1 or n==1:
# return 1
# else:
# return self.uniquePaths(m-1,n)+self.uniquePaths(m,n-1)
dp=[[0]*(n+1) for i in range(m+1)]
for i in range(1,m+1):
for j in range(1,n+1):
if i==1:
dp[i][j]=1
continue
if j==1:
dp[i][j]=1
continue
dp[i][j]=dp[i-1][j]+dp[i][j-1]
return dp[m][n]
例题4.矩阵最小路径和
输入:[[1,3,5,9],[8,1,3,4],[5,0,6,1],[8,8,4,0]]
返回值:12
class Solution:
def minPathSum(self , matrix: List[List[int]]) -> int:
# write code here
#输出所有的路径中最小的路径和。
m=len(matrix)
n=len(matrix[0])
dp=[[0]*(n+1) for i in range(m+1)]
#我们可以构造一个与矩阵同样大小的二维辅助数组dp[i][j]表示以当前i,j位置为终点的最短路径长度
dp[0][0] = matrix[0][0]
#很容易知道第一行与第一列,只能分别向右或向下,没有第二种选择,因此第一行只能由其左边的累加,第一列只能由其上面的累加
#处理第一列
for i in range(1, n):
dp[i][0] = matrix[i][0] + dp[i - 1][0]
#处理第一行
for j in range(1, m):
dp[0][j] = matrix[0][j] + dp[0][j - 1]
#其他就取其中较小值与当前位置的值相加就是到当前位置的最小路径和
for i in range(1,m):
for j in range(1,n):
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+matrix[i][j]
return dp[n - 1][m - 1]