判断子序列&&不同的子序列&&两个字符串的删除操作&&编辑距离

引言

下面的四种题相互间都有联系,都是类似编辑距离类的题目,这里从简单开始,逐渐深入;

判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

示例 1:
输入:s = “abc”, t = “ahbgdc”
输出:true

示例 2:
输入:s = “axc”, t = “ahbgdc”
输出:false

提示:

0 <= s.length <= 100
0 <= t.length <= 10^4
两个字符串都只由小写字符组成。

这道题我们其实只需要计算删除的情况就可以;(其实可以想象为把 t 的每一个和 s 不相等的字符删除直到和 s 一样)
1,dp[i][j]表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度;

2,这里就有两种情况:
s[i - 1] == t[j - 1] 此时就说明 s 中的一个字符在 t 中存在;即dp[i][j] = dp[i - 1][j - 1] + 1;
s[i - 1] != t[j - 1] 这时就需要删除 t 的这个字符,继续和 s 匹配;即dp[i][j] = dp[i][j - 1]

3,初始化时发现dp[i][0]和dp[0][j]是没有意义的,但是为了转移方程的计算,还是需要初始化为0;

4,由转移方程可知道遍历顺序是从前到后,从上到下;
代码如下:

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int len1 = s.size(), len2 = t.size();
        vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
        for (int i = 1; i <= len1; ++i) {
            for (int j = 1; j <= len2; ++j) {
                if (s[i - 1] == t[j - 1]) 
                dp[i][j] = dp[i - 1][j - 1] + 1;
                else
                dp[i][j] = dp[i][j - 1]; //j相当于一个删除的操作;
            }
        }
        return dp[len1][len2] == len1;
    }
};

这道题只涉及了一个类似删除操作,因为t中字符与s一个不同就删一个,直到留下最后相同的;下面再看一道:

不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
题目数据保证答案符合 32 位带符号整数范围。

在这里插入图片描述
在这里插入图片描述

提示:

0 <= s.length, t.length <= 1000
s 和 t 由英文字母组成

这道题相对上一道就稍微复杂一些,但是依然可以看出只涉及了删除操作;
1,dp[i][j]表示以 i-1 为结尾的 s 子序列中出现的以 j-1 为结尾的 t 的个数为dp[i][j];

2,同样分为两种情况:

当s[i - 1] 与 t[j - 1]相等时,有两部分
一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1];
另一部分是不用s[i - 1]匹配,个数为dp[i - 1][j];
为什么不用s[i - 1]匹配呢?比如s为raa,t为ra,我可以用s[i - 1]匹配,则是s[0],s[2],我如果不用s[i - 1]匹配,则是s[0],s[1];
所以这种情况都需要概况上;
即dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]:

当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j],
其实这里 i - 1 就可以理解为一个删除操作,将因为不匹配,所以减小匹配范围

3,初始化这里需要注意,
dp[i][0]表示以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
则dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。

dp[0][j]则是s为空,那么怎么都无法匹配到t,所以都为0;

dp[0][0] 则代表空字符串s,删除0个元素变成t,所以为1;

4,遍历顺序很容易就看出来从前到后,从上到下;

代码如下:

class Solution {
public:
    int numDistinct(string s, string t) {
        int len1 = s.size(), len2 = t.size();
        vector<vector<unsigned long long>> dp(len1 + 1, vector<unsigned long long>(len2 + 1));//防止超限
        for (int i = 0; i <= len1; ++i) dp[i][0] = 1;//i为0是因为要把dp[0][0]初始化为0
        for (int i = 1; i <= len2; ++i) dp[0][i] = 0;
        for (int i = 1; i <= len1; ++i) {
            for (int j = 1; j <= len2; ++j) {
                if (s[i - 1] == t[j - 1])
                dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                else
                dp[i][j] = dp[i - 1][j];
            }
        }
        return dp[len1][len2];
    }
};

这道题也就有点难度了,但是总归还是只有一个删除操作,看下面这道题

两个字符串的删除操作

给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。

示例:
输入: “sea”, “eat”
输出: 2
解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"

提示:
给定单词的长度不超过500。
给定单词中的字符只含有小写字母。

这道题目就需要删除两个字符串了,但是方法还是一样的;
1,dp[i][j]表示以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数

2,这里的转移方程依旧分为两种情况:
当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];

当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
最后取三者最小值即可,dp[i][j] = min(min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 2);

3,初始化需要注意
当word2为空串,那么dp[i][0]就是以i-1为结尾的字符串word1要删除多i个元素,才能和word2相同,即dp[i][0] = i。
当word1为空串,那么dp[0][j]就是以j-1为结尾的字符串word2要删除j个元素,才能和word1相同,即dp[i][0] = i。

4,遍历顺序很容易看出来,从上到下,从左到右;

代码如下:

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.size(), len2 = word2.size();
        vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1));
        for (int i = 0; i <= len1; ++i) dp[i][0] = i;
        for (int i = 0; i <= len2; ++i) dp[0][i] = i;
        for (int i = 1; i <= len1; ++i) {
            for (int j = 1; j <= len2; ++j) {
                if (word1[i - 1] == word2[j - 1])
                dp[i][j] = dp[i - 1][j - 1];
                else
                dp[i][j] = min(min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 2);
            }
        }
        return dp[len1][len2];
    }
};

最后的大boss终于要来了,但是通过前三道题,应该对这种题目有点感觉了吧;

编辑距离

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

示例 1:
输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)

示例 2:
输入:word1 = “intention”, word2 = “execution”
输出:5
解释:
intention -> inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention -> exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)

提示:

0 <= word1.length, word2.length <= 500
word1 和 word2 由小写英文字母组成

这道题就有了三步操作,但是不管怎么样,和之前的几道题都是一模一样的;
1,dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j];

2,这里还是两种情况
如果word1[i - 1] == word2[j - 1]时,不需要任何操作,直接让dp[i][j] = dp[i - 1][j - 1];

如果word1[i - 1] != word2[j - 1],这时候分为三部,对应的就是三种操作
一:word1增加一个元素,使word1[i - 1]与word2[j - 1]相同,即 dp[i][j] = dp[i - 1][j] + 1
二:word1减去一个元素,即word2增添一个元素,使word1[i - 1]与word2[j - 1]相同,即 dp[i][j] = dp[i][j - 1] + 1;
三:替换操作,word1替换word1[i - 1],使其与word2[j - 1]相同,即 dp[i][j] = dp[i - 1][j - 1] + 1;

3,初始化dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,则只需要对word1做减去操作即可
dp[i][0] = i;
dp[0][j] = j同理;

4,循环顺序老套路;
代码如下:

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.length();
        int len2 = word2.length();
         vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1));
        for (int j = 1; j <= len2; ++j) dp[0][j] = j;
        for (int i = 1; i <= len1; ++i) dp[i][0] = i;
        for (int i = 1; i <= len1; ++i) {
            for (int j = 1; j <= len2; ++j) {
                if (word1.at(i - 1) == word2.at(j - 1))
                dp[i][j] = dp[i - 1][j - 1];
                else
                dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
            }
        }
        return dp[len1][len2];
    }
};

总结

这几道题还是多看看,理解一下,看不下去歇会看,看久了容易晕;

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YXXYX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值