动态规划解题思路

动态规划

在这里插入图片描述
动态规划最重要的是掌握他的思想,动态规划的核心思想是把原问题分解成子问题进行求解,也就是分治的思想。

1.理论知识

  • 特点1:存在重复的子问题,所以需要保存之前的计算结果
  • 特点2:最优子结构,最优解肯定是有最优的子解转移推导而来,子解必定也是子问题的最优解。
  • 特点3:无后效性:求出来的子问题并不会因为后面求出来的改变。
  • 思考方向:自顶而下,使用递归+记忆化; 自底向上:递推求解

2.状态

状态的定义,先尝试「题目问什么,就把什么设置为状态」;

3.状态转移方程

然后思考「状态如何转移」,如果「状态转移方程」不容易得到,尝试修改定义,目的依然是为了方便得到「状态转移方程」。

有部分问题是线性问题,可以直接递推求解。但是大多数都是有 分类讨论,把当前问题划分为几个子问题,这些子问题的最优解构成了当前的问题的最优解。

4.初始化

从定义出发,初始化最小的问题,从而为后面的递推打下基础。有的初始化需要对DP table多加一行,或者多加一列来防止越界。

5.输出

一般来说输出都是dp table的最后一个,但是有时候不一定。是要根据定义的符号关系和递推顺序进行的,才能确定哪一个才是最后的输出。

6.空间优化

如果当前行状态只于上一行的有关,那么二维dp矩阵可以使用一维替代。
如果当前值只于上一个值有关, 那么有的一维dp可以使用某几个变量替代,具体问题要具体分析。


题目的分类整理


DP表

5. 最长回文子串

题目:定一个字符串 s,找到 s 中最长的回文子串。
建立二维dp,自底而上,自左到右。

  • 当前字符相等,看其内部的字符是否是回文
  • 邻字符相等就直接赋值True
    d p [ i ] [ j ] = { d p [ i + 1 ] [ j − 1 ] , s [ i ] = = s [ j ] T r u e , j = = i + 1 F a s l e , e l s e dp[i][j]= \begin{cases} dp[i+1][j-1], & s[i] == s[j] \\ True, & j == i+1\\ Fasle, &else \end{cases} dp[i][j]=dp[i+1][j1],True,Fasle,s[i]==s[j]j==i+1else

62. 不同路径

题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?

  • 这道题机器人只能向右和向下走,所以动态转移方程很容易推出:
    d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j] = dp[i-1][j]+dp[i][j-1] dp[i][j]=dp[i1][j]+dp[i][j1]
    初始化每一个网格内部的值为1

63. 不同路径 II

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1 和 0 来表示。

  • 开始考虑过回溯,但是超时。
  • 这道题相对于62题多了一个障碍物,可以使用DP table,建立一个初始全1的DP表,遇到障碍物就当前路径数清零
    具体方法:
  • 初始第一行,遇到0,之后的第一行DP表内参数都变为0,不可达到。
  • 初始第一列,遇到0,DP表内第一列0之后的参数为0, 不可达到。
    d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] , i f   g r i d [ i ] [ j ] ! = 0 0 , g r i d [ i ] [ j ] = 0 dp[i][j]= \begin{cases} dp[i-1][j]+dp[i][j-1],& if \: grid[i][j] != 0\\ 0,&grid[i][j] = 0 \end{cases} dp[i][j]={ dp[i1][j]+dp[i][j1],0,ifgrid[i][j]!=0grid[i][j]=0

64. 最小路径和

题目:给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。

  • 建立m*n的一个DP表
  • 初始化第一行,对第一行元素累加
  • 初始化第一列,对第一列元素累加
  • 之后就开始进行递推
    d p [ i ] [ j ] = m i n { d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] } + d p [ i ] [ j ] dp[i][j] = min\{dp[i-1][j], dp[i][j-1]\} + dp[i][j] dp[i][j]=min{ dp[i1][j],dp[i][j1]}+dp[i][j]

72. 编辑距离

题目:给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符

  • 建立一个(m+1)*(n+1)的矩阵用于dp,dp值表示当前位置所需要的最小操作次数。
  • 初始化,将单词1索引放在第一行,单词2索引放在第一列,dp[0][0] = 0, 还未操作的。
    动态转移方程如下:
    d p [ i ] [ j ] = { m i n { d p [ i − 1 ] [ j ] + 1 , 删 除 操 作 d p [ i ] [ j − 1 ] + 1 , 插 入 操 作 d p [ i − 1 ] [ j − 1 ] + 1 , 替 换 操 作 d p [ i − 1 ] [ j − 1 ] , w o r d 1 [ i ] = w o r d 2 [ j ] dp[i][j]= \begin{cases} min\begin{cases} dp[i-1][j]+1, & 删除操作\\ dp[i][j-1]+1, & 插入操作\\ dp[i-1][j-1]+1,&替换操作\\ \end{cases}\\ dp[i-1][j-1], \quad \quad word1[i]=word2[j] \end{cases} dp[i][j]=mindp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+1,dp[i1][j1],word1[i]=word2[j]
    解释一下为什么:
  • 删除操作:由于本次word2[j] 不匹配word[i],想要删除word1[i],让word2[j]去匹配word1[i+1],所以 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + 1 dp[i][j] = dp[i-1][j]+1 dp[i][j]=dp[i1][j]+1,加的是一个删除的操作步数。
  • 插入操作:由于本次word2[j] 不匹配word[i],想要插入一个word2[j]使得两个相等,就匹配下一个word2[j+1] 和word1[i],所以word2的索引后移, d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + 1 dp[i][j] = dp[i][j-1]+1 dp[i][j]=dp[i][j1]+1,加的是插入的操作数。
  • 替换操作:替换就是让word2[j] 在不等于 word1[i]的时候等于word1[i],此时两者都要后移索引,所以 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i-1][j-1] dp[i][j]=dp[i1][j1]
  • 如果当前值相等,无需加操作数,两者索引同时后移。

87. 扰乱字符串

题目:给出两个长度相等的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串。
示例 1:输入: s1 = “great”, s2 = “rgeat” 输出: true

  • 建立一个三维DP,大小为 n x n x (n+1), dp[i][j][l],表示字符串S[i:i+l]与字符串T[j:j+l]是否满足扰乱关系。
  • 建立的时候所有值都是False; 初始化在立方体的底面,也就是字符串单个字符与另一个字符串单个字符的关系。
  • dp推理:进行枚举
    • 枚举字符长度2-n中的索引 k
    • 枚举S中的起始位置 i
    • 枚举T中的起始位置 j
    • 枚举划分的位置 w
    • 枚举两种扰乱的情况

d p [ i ] [ j ] [ k ] = { T r u e , i f d p [ i ] [ j ] [ w ] = d p [ i + w ] ] [ j + w ] [ k − w ] T r u e , i f d p [ i + w ] [ j ] [ k − w ] = d p [ i ] [ j + k − w ] [ w ] F a l s e , e l s e dp[i][j][k]= \begin{cases} True, &if \quad dp[i][j][w] = dp[i+w]][j+w][k-w]\\ True, &if \quad dp[i+w][j][k-w] = dp[i][j+k-w][w]\\ False, & else \end{cases} dp[i][j][k]=True,True,False,ifdp[i][j][w]=dp[i+w]][j+w][kw]ifdp[i+w][j][kw]=dp[i][j+kw][w]else

97. 交错字符串

题目:给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的。
示例 1:输入: s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbcbcac”; 输出: true

  • dp建立:这道题还是建立dp表格进行求解,dp[ i ][ j ] 表示 s1的前 i - 1个和 s2的前 j - 1 个,能够组合的字符串与s3[ i+j-1]之前的i+j-2个字符进行匹配,由于字符的匹配是逐个匹配,要么选择s1.要么选择s2,所以可以使用这么一个转移关系。
  • 初始化:开始的时候需要对dp table的第一行,第一列进行初始化,初始化的意思就是只考虑单个的字符串,将是s1或者s2 与s3单独逐个进行匹配。
  • 这里的转移方程为要么选择s1,s1右移,要么选择s2,s2右移,有一个匹配成功就为True.
    d p [ i ] [ j ] = { d p [ i − 1 ] [ j ]   a n d   s 1 [ i − 1 ] = = s 3 [ i + j − 1 ] o r d p [ i ] [ j − 1 ]   a n d   s 2 [ j − 1 ] = = s 3 [ i + j − 1 ] dp[i][j]= \begin{cases} dp[i-1][j] \: and \: s1[i-1]==s3[i+j-1] \\ \quad \quad \quad \quad \quad \quad or\\ dp[i][j-1] \:and \: s2[j-1]==s3[i+j-1] \end{cases} dp[i][j]=dp[i1][j]ands1[i1]==s3[i+j1]ordp[i][j1]ands2[j1]==s3[i+j1]

115. 不同的子序列

题目:给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。例如:S = “rabbbit”, T = “rabbit”,输出3。

  • 建立一个(row+1)*(col+1)的dp矩阵.dp[i][j]表示T[:i]在S[:j]内出现的次数。
  • 初始化第一行为1, 空字符在第一次相等,之后就没有空字符,表示只有这一种取法.
  • 分T[i] == S[j]和T[i] != S[j]两种情况。
    • 不相等:dp[i][j] = dp[i][j-1]
    • 相等:dp[i][j] = dp[i][j-1]+dp[i-1][j-1]

相等的时候就是,使用T[i]与S[:j-1]进行匹配的个数加上不使用T[i]与S[:j-1]匹配的个数。
动态转移方程
d p [ i ] [ j ] = { d p [ i ] [ j − 1 ] , S [ i ] ≠ T [ j ] d p [ i − 1 ] [ j − 1 ] + d p [ i ] [ j − 1 ] , S [ i ] = T [ j ] dp[i][j]= \begin{cases} dp[i][j-1], &S[i] \ne T[j] \\ dp[i-1][j-1]+dp[i][j-1],&S[i]=T[j] \end{cases} dp[i][j]={ dp[i][j1],dp[i1][j1]+dp[i][j1],S[i]=T[j]S[i]=T[j]

123. 买卖股票的最佳时机 III

题目:给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)

  • 最多交易两次,可以选择交易0次或者1次。
  • 初始化,由于是三维dp数组,所以初始化是初始化的一个底面:
    • dp[0][0][0] = 0 #第一天,啥都没做
    • dp[0][0][1] = -float(‘INF’) #第一天卖了一次,不存在
    • dp[0][0][2] = -float(‘INF’) #第一天卖了两次,不存在
    • dp[0][1][0] = -prices[0] #第一天买一次了,但是没卖,但是有股票
    • dp[0][1][1] = -float(‘INF’) #第一天卖一了次,还有股票,不存在
    • dp[0][1][2] = -float(‘INF’) #第一天卖了两次,还有股票不存
  • 分析状态:一天结束时,可能有持股、可能未持股、可能卖出过1次、可能卖出过2次、也可能未卖出过所以定义状态转移数组dp[天数][当前是否持股][卖出的次数] = dp[ i ][ j ][k].
    { d p [ i ] [ 0 ] [ 0 ] = 0 , 未 持 股 未 卖 出 d p [ i ] [ 0 ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] [ 0 ] + p [ i ] , d p [ i − 1 ] [ 0 ] [ 1 ] ) , 今 天 卖 出 或 者 之 前 卖 出 d p [ i ] [ 0 ] [ 2 ] = m a x ( d p [ i − 1 ] [ 1 ] [ 1 ] + p [ i ] , d p [ i − 1 ] [ 0 ] [ 2 ] ) , 今 天 卖 出 或 者 之 前 卖 出 d p [ i ] [ 1 ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] [ 0 ] − p [ i ] ,
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值