2022.12.1 am

【刷dp题目】//今天的题目不太会,直接学吧。

1.判断子序列(392题):

本题把dp数组定义成布尔类型不太方便,定义成:以下标i-1为结尾的字符串s和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。这样最后只用判断dp[s.size()][t.size()] == s.size(),如果相等就说明s是t的子序列

这个题相当于编辑距离的一个简单版本,在遍历s和t的时候有两种情况:s[i - 1] == t[j - 1](说明t中找到了一个字符在s中也出现了,那么这个时候就是在上一个字符的基础上加一,也就是dp[i][j] = dp[i - 1][j - 1] + 1)和s[i - 1] != t[j - 1](说明t中需要删除元素,因为s是必须每个字符都匹配上的,那这个t中元素和s中元素不相同,只能把t中的元素删除,也就是此处的相同子序列长度还是t以j-2位置结尾的那个长度,dp[i][j] = dp[i][j - 1])。

2.不同的子序列(115题):

本题是要求s中t的个数了。定义为:以i-1为结尾的s中出现以j-1为结尾的t的个数为dp[i][j]。还是有两种情况:s[i - 1] == t[j - 1]和s[i - 1] != t[j - 1]。

当s[i - 1] == t[j - 1]时,有两种情况,一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1],一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。例如s:bagg和t:bag,s[3]和t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即s[0]s[1]s[2]组成的bag。当然也可以用s[3]来匹配,即s[0]s[1]s[3]组成的bag。

当s[i - 1] != t[j - 1]时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即dp[i - 1][j]

初始化:dp[i][0] = 1(s中出现空串的个数就是1,相当于删除所有元素)。dp[0][j] = 0(空串中出现任何字符串的个数都是0)。另外dp[0][0]应该也是1。

另外就是有测例很长,所以dp数组里数据用到uint64_t。

3.两个字符串的删除操作(583题):

比起上一题,这个题是两个字符串都可以删了(上一题t是不能删的,只能删s)。还有一点不同,上一题求的是不同的个数,也就是说我们不追求一个最小值,所以即使相等的时候我们也是有两种方式(用它匹配或不用它匹配),而这一题求的是最少的操作次数,那相等的时候就不要麻烦了,一定要用它匹配。

当word1[i - 1]与word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1]; 也就是没有加操作数。

当word1[i - 1]与word2[j - 1]不相同的时候,可以删word1[i - 1]或者word2[j - 1],dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1); 也就是操作数要加上一了。

初始化还是dp[0][j] = j,dp[i][0] = i。

4.编辑距离(72题):

上面的题目都是只能做删除操作。本题有三种操作:插入、删除、替换。也是要将word1转换成word2,求最少步数(叫编辑距离)。dp[i][j]表示以下标i-1结尾的word1和以下标j-1结尾的word2的最近编辑距离

如果word[i - 1] == word2[j - 1],还是不用操作,dp[i][j] = dp[i - 1][j - 1]。

如果word[i - 1] != word2[j - 1]现在就有增删换三种情况可以操作了。①word1删除一个元素,即i-2结尾的word1与j-1结尾的word2的最近编辑距离再加上一个操作。②word2删除一个元素(其实一方删除和另一方添加是一个效果,最终的操作数是一样的),即i-1结尾的word1与j-2结尾的word2的最近编辑距离再加上一个操作。③替换元素,即i-2结尾的word1与j-2结尾的word2的最近编辑距离再加上一个替换元素的操作。最终取最小的操作数:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;

初始化依然和上面一样的。

//以上四道题其实都是类似的,第一题第二题只能删除其中一个字符串中的元素,第三题可以删两个字符串中的元素了,第四题的操作变成了三种(但其实插入可以转换为删除)。而且一般这种题都是定义为以i-1和j-1结尾的,这样比较好写。

5.回文字串(647题):

dp[i][j]表示区间范围[i,j](左闭右闭)的子串是否是回文子串(布尔类型)。

整体情况还是两种,s[i] != s[j]的时候肯定是dp[i][j] = false,那s[i] == s[j]的时候就要分情况了,①i和j相等,也就是就一个字符,例如a,那肯定是回文子串,②i和j相差1,例如aa,也是回文子串,③i和j相差大于1,例如cabac,我们此时就要看dp[i + 1][j - 1]是否为true

本题的遍历顺序,根据递推关系,dp[i][j]依赖dp[i + 1][j - 1],那i要从大到小遍历,j要从小到大遍历才行。当然一定要注意定义是[i,j],要求j>=i。

然后本题要求的是数量,所以维护一个result来记录数量。每次出现一个dp为true的时候,也就是又找到了一个回文子串,result++。

另外,这个题其实用双指针法,空间复杂度会更小(时间复杂度都是O(N^2))。通过中心点向两边扩散看是不是对称的。那么中心点有两种情况,一个元素或者两个元素都可以作为中心点。所以可以分开调用函数计算。遍历s,以i为中心,以i和i+1为中心,所有i的这两个结果都要加到result上。那么扩散的这个函数其实就是双指针,传入s,两个中心点(如果是一个就传一样的),s的结尾处(这样比较方便)。然后while循环,中心点开始扩散,两个对称就结果加一,然后继续扩散,不对称了就结束。

6.最长回文子序列(516题):

本题就是不要求连续了,定义为:字符串s在[i,j]范围内最长的回文子序列的长度为dp[i][j]。注意是长度,所以如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;(注意是加2)。如果s[i]与s[j]不相同,说明s[i]和s[j]同时加入并不能增加[i,j]区间回文子串的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列,即dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);,那么根据递推关系可以看出来,i要从大到小遍历,j要从小到大遍历。

初始化:首先要考虑当i和j相同的情况,从递推公式可以看出,递推公式是计算不到i和j相同时候的情况(因为j要从i+1开始遍历,只会填充正方形的右上半部分,而右上半部分需要对角线初始化),所以需要手动初始化一下,当i与j相同,那么dp[i][j]一定是等于1的,即一个字符的回文子序列长度就是1。

本题主要容易出错的问题就是要初始化对角线(因为要根据对角线填充右上角的位置),还有遍历的时候j从i+1开始

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值