Bootstrap

DAY50 || 115.不同的子序列 |583. 两个字符串的删除操作 | 72. 编辑距离 | 编辑距离总结篇

115.不同的子序列(只能删除s)

115. 不同的子序列 - 力扣(LeetCode)

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)

题目数据保证答案符合 32 位带符号整数范围。

115.不同的子序列示例

提示:

  • 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]相等。dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
  • s[i - 1] 与 t[j - 1] 不相等。dp[i][j] = dp[i - 1][j];(好理解,不匹配则删掉一个s,继续看前一个)
  • // 当此元素相等时, 则求i-1个s前面有多少个"ba" + i-1个s前面有多少个完整"bag" // 若此元素不相等, 则求s的前面有多少个"bag"

假设当前遍历到的 t 是 bag,s 是 babga,此时 s 又来了一个 g, 和当前 t 的最后一个元素相同了。这个时候新的 babgag 含有 bag 的数量||是在 babga 原本包含的 bag 数量(dp【i - 1】【j】)的基础上,增加了使用新来的 g 新组成的 bag 数量。新来的 g 能组成多少个bag,其实是原本的 babga 包含多少个 ba。也就是 dp【i - 1】【j - 1】。 

3.初始化,dp[0][0]=0;其他第一列都为0(因为和空字符的交集都是1)第一行都是0,因为空的s中永远只有0个t。

4.递推方向

5.举例

 

代码 

class Solution {
public:
    int numDistinct(string s, string t) {
        //dp表示前i-1的s中有多少个前j-1的t
        vector<vector<uint64_t>>dp(s.size()+1,vector<uint64_t>(t.size()+1));

        for(int i=0;i<s.size();i++)dp[i][0]=1;//初始化,和空字符的交集个数是1
        for(int j=1;j<t.size();j++)dp[0][j]=0;//0个s和t的交集是0,例外,dp[0][0]=0

        for(int i=1;i<=s.size();i++)
        {
            for(int j=1;j<=t.size();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[s.size()][t.size()];
        
    }
};

583. 两个字符串的删除操作

583. 两个字符串的删除操作 - 力扣(LeetCode)

给定两个单词 word1 和 word2 ,返回使得 word1 和  word2 相同所需的最小步数

每步 可以删除任意一个字符串中的一个字符。

示例 1:

输入: word1 = "sea", word2 = "eat"
输出: 2
解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"

示例  2:

输入:word1 = "leetcode", word2 = "etco"
输出:4

写法一(记录删除次数)

真的很像爬楼梯和装背包,应该说是01背包吧。都有前面的基础上加上有新元素的方法。

1.dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。 

2.

  • 当word1[i - 1] 与 word2[j - 1]相同的时候
  • 当word1[i - 1] 与 word2[j - 1]不相同的时候

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

那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});

因为 dp[i][j - 1] + 1 = dp[i - 1][j - 1] + 2,所以递推公式可简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);

3.初始化操作。若一个为空,则要删除另一个的大小(次数)才能相同。所以第一行全是j,第一列全是i。

5.举例

代码 

class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>>dp(word1.size()+1,vector<int>(word2.size()+1));
        for(int i=0;i<=word1.size();i++)dp[i][0]=i;//第一列
        for(int j=0;j<=word2.size();j++)dp[0][j]=j;//第一行
       

        for(int i=1;i<=word1.size();i++)
        {
            for(int j=1;j<=word2.size();j++)
            {
                if(word1[i-1]==word2[j-1])
                dp[i][j]=dp[i-1][j-1];//相同,不需要删除操作
                else
                dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1);//比较删除任何一个,取最小值
            }
        }
        return dp[word1.size()][word2.size()];
        
    }
};

写法二(记录最大公共长度,间接法)

最后统计剩余元素个数即可。

代码

class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>>dp(word1.size()+1,vector<int>(word2.size()+1));
       
        for(int i=1;i<=word1.size();i++)
        {
            for(int j=1;j<=word2.size();j++)
            {
                if(word1[i-1]==word2[j-1])
                dp[i][j]=dp[i-1][j-1]+1;//记录相同元素
                else
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);//不相同,则比较前面哪个重合最多元素
            }
        }
        return word1.size()+word2.size()-dp[word1.size()][word2.size()]*2;//得删除元素个数
        
    }
};

72. 编辑距离(都是为了本题服务)

72. 编辑距离 - 力扣(LeetCode)

给你两个单词 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')

 思路

做过了前面题目,本题真不难

1.dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

2.

·if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,dp[i][j] 就应该是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];

·if (word1[i - 1] != word2[j - 1]),此时就需要编辑了,如何编辑呢?

  • 操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 再加上一个操作。

即 dp[i][j] = dp[i - 1][j] + 1;

  • 操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 再加上一个操作。(相当于word1添加元素)

即 dp[i][j] = dp[i][j - 1] + 1;

  ·操作三:替换元素,操作加1.所以 dp[i][j] = dp[i - 1][j - 1] + 1;

然后取三者最小值。

初始值和遍历顺序与前面一样操作。

举例

 

代码

class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>>dp(word1.size()+1,vector<int>(word2.size()+1,0));

        for(int i=0;i<=word1.size();i++)dp[i][0]=i;//第一列
        for(int j=0;j<=word2.size();j++)dp[0][j]=j;//第一行

        for(int i=1;i<=word1.size();i++)
        {
            for(int j=1;j<=word2.size();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]+1,dp[i-1][j]+1),dp[i][j-1]+1);//替换操作,删除操作,插入操作(删除数组2的元素相当于插入元素进入数组1)
                }
            }
        }        
        return dp[word1.size()][word2.size()];
    }
};

;