在leetcode题库中,动态规划标签下题目数量稳居各标签下题目数量前列。想要学习算法或找工作笔试,动态规划是一个避不开的知识点。
但是,只要了解动态规划的解题步骤,大部分的动态规划题目求解并不会太困难。
0. 动态规划 简介
- 动态规划思想:将原问题拆解成重复子问题,然后递归地找到每个子问题的最优解,最后找到全局最优解。
- 关键点:初始状态、状态转移方程、边界条件
- 解题三步走:
- 判断是否适用
- 考虑能否将问题规模减小
- 状态间是否存在关联
- 总结状态转移方程
- 一维动态规划
- 二维动态规划
- 区间动态规划
- 确定边界条件
- 判断是否适用
话不多说,我们看看leetcode题目!
1. 一维动态规划 (300.最长上升子序列)
1.1 判断是否使用DP(动态规划)
当输入数组为nums=[10] 时,最长上升子序长度为1 ([10]);
当输入数组为nums=[10,15] 时,最长上升子序长度为2 ([10,15]);
当输入数组为nums=[10,15,11] 时,最长上升子序长度为2 ([10,11]);
当输入数组为nums=[10,15,11,12] 时,最长上升子序长度为3 ([10,11,12]);
- 可以看出,原问题是可以被拆分为多个重复子问题迭代运算的,即动态规划算法适用。
1.2 状态转移方程
从上面描述中可以看出,只需判断新加入元素e与nums中每一个元素的大小关系,即可计算新的最长上升子序长度。采用dp[i]记录nums[0:i]的最长上升子序长度。例如,
- 当nums=[10],dp[0]=1;
nums=[10,15],dp[1]=2
nums=[10,15,11],dp[2]=2
当新元素12加入nums=[10,15,11]时,则分别比较12与10、15、11的大小关系,如果12>nums[j] (0≤j<≤2),则dp[3]=dp[j]+1。
所以状态转移方程: d p [ i ] = M a x ( d p [ j ] ) + 1 , 其 中 0 ≤ j < i 且 n u m s [ j ] < n u m s [ i ] dp[i] = Max\left(dp[j]\right)+1 , 其中0≤j<i且nums[j]<nums[i] dp[i]=Max(dp[j])+1,其中0≤j<i且nums[j]<nums[i]
1.3 边界情况
此题的边界条件比较简单,当nums长度为1,最长上升子序长度为1,即dp[0]==1。
解题过程动画
代码实现:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
dp = [1] * len(nums)
# 遍历nums元素,计算以当前元素结尾时的最长上升子序长度
for i in range(len(nums)):
tmpmax = 1
# 比较新加入元素与第0到第(i-1)个元素的大小,并更新最长上升子序长度
for j in range(0 ,i):
if nums[j] < nums[i]:
if tmpmax < dp[j] + 1:
tmpmax = dp[j] + 1
dp[i] = tmpmax
return max(dp)
2. 二维动态规划 (10.正则表达式匹配)
2.1 判断是否使用DP(动态规划)
如果,s = “abc”, p = “abc”,那么s、p是否可以匹配,则决定于s2 = "ab"和p2 = "ab"是否匹配,以及字符串s的最后一个字母与字符串s最后一个字母是否匹配。即原问题可以拆分为重复子问题。
2.2 状态转移方程
假设当前子问题下,字符串s的长度为i-1,字符串p的长度为j-1。根据题目可知字符串p最后一个字符的取值有三种情况。
- p的最后一个字符是字母,那么如果s[i]==p[j],且s[0:i-1]与p[0:j-1]匹配,则s与p匹配。
- p的最后一个字符是".",那么只要s[0:i-1]与p[0:j-1]匹配,则s与p匹配。
- p的最后一个字符是"* ",那么表示p的第j−1个字符匹配任意自然数次(包括0次);
- 如果匹配0次,只要s[0:i]与p[0:j-2]匹配,则s与p匹配;
- 如果匹配非0次,需要s[i]==p[j-1] (p[j-1]为"."),且s[0:i-1]与p[0:j]匹配,才满足s与p匹配。
使用二维数组dp[i][j]表示s的前i个字符与p中的前j个字符是否能够匹配,取值布尔类型。则状态转移方程:
2.3 边界情况
- 若字符串s长度为i,p长度为j,数组dp长度(i+1)*(j+1),dp[0][0]
=True,表示两个空字符串匹配。 - dp[0][k]表示字符串s为空,p不为空情况,需要提前对该行进行初始化。
代码实现:
class Solution:
def isMatch(self, s: str, p: str) -> bool:
len_p = len(p)
len_s = len(s)
matrix = [[False for i in range(len_s+1)] for j in range(len_p+1)]
matrix[0][0] = True
k = 0
for i in range(len_p):
if p[i]=='*':
k = k+1
if k*2 > i:
matrix[i+1][0] = True
for i in range(1, len_p+1):
for j in range(1, len_s+1):
if p[i-1] == s[j-1] or p[i-1]==".":
matrix[i][j] = matrix[i-1][j-1]
elif p[i-1] == "*":
matrix[i][j] = matrix[i - 2][j]
if p[i-2] == s[j-1] or p[i-2]=='.':
matrix[i][j] |= matrix[i][j-1]
else:
matrix[i][j] = False
return matrix[len_p][len_s]
更多内容欢迎关注个人公众号:PythonAndDataTech