LeetCode刷题笔记 动态规划 字符串编辑问题

72 编辑距离

​ 给定两个字符串,已知你可以删除、替换和插入任意字符串的任意字符,求最少编辑几步可以将两个字符串变成相同。

​ 输入是两个字符串,输出是一个整数,表示最少的步骤。

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

解析:

​ 本题与最长公共子序列类似,两个字符串的对比所以需要使用一个二维数组来设置状态 dp[i][j]

​ 设置状态:dp[i][j] 表示将第一个字符串到位置 i 为止,和第二个字符串到位置 j 为止,最多需要几步编辑。

​ 状态转移方程:当第 i 位和第 j 位对应的字符相同时,dp[i][j] 等于 dp[i-1][j-1];当二者对应的字符不同时,修改的消耗是 dp[i-1][j-1]+1,插入 i 位置/删除 j 位置的消耗是 dp[i][j-1] + 1,插入 j 位置/删除 i 位置的消耗是 dp[i-1][j] + 1,所以在不同的情况下状态转移方程dp[i][j] = min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])

​ 边界情况:word1为空串时,需要插入字符与word2一致,即dp[0][j] = j;同理,word2为空串是dp[i][0] = i

class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.length(), n = word2.length();
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        // 边界条件 word2为空串
        for(int i=0;i<=m;++i){
            dp[i][0] = i;
        }
        // 边界条件 word1为空串
        for(int j=0;j<=n;++j){
            dp[0][j] = j;
        }
        for(int i=1;i<=m;++i){
            for(int j=1;j<=n;++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],min(dp[i-1][j],dp[i][j-1])) + 1;
                }
            }
        }
        return dp[m][n];
    }
};

650 只有两个键的键盘

​ 给定一个字母 A,已知你可以每次选择复制全部字符,或者粘贴之前复制的字符,求最少需要几次操作可以把字符串延展到指定长度。

​ 输入是一个正整数,代表指定长度;输出是一个整数,表示最少操作次数。

输入:3
输出:3
解释:
最初, 只有一个字符 ‘A’。
第 1 步, 使用 Copy All 操作。
第 2 步, 使用 Paste 操作来获得 ‘AA’。
第 3 步, 使用 Paste 操作来获得 ‘AAA’。

解析:

​ 不同于以往通过加减实现的动态规划,这里需要乘除法来计算位置,因为粘贴操作是倍数增加的。

​ 设置状态:使用一个一维数组 dp[i],其中位置 i 表示延展到长度 i 的最少操作次数。

​ 状态转移方程:对于每个位置 j,如果 j 可以被 i 整除,那么长度 i 就可以由长度 j 操作得到,其操作次数等价于把一个长度为 1 的 A 延展到长度为 i/j。例如3由1操作得到A A A,6由2操作得到AA AA AA因此可以得到递推公式 dp[i] = dp[j] + dp[i/j]

class Solution {
public:
    int minSteps(int n) {
        vector<int> dp(n+1);
        for(int i=2;i<=n;++i){
            dp[i] = i;
            for(int j=2;j<=i;++j){
                if(i%j==0){
                    dp[i] = dp[j] + dp[i/j];
                    break;
                }
            }
        }
        return dp[n];
    }
};

10 正则表达式匹配

​ 给定一个字符串和一个正则表达式(regular expression, regex),求该字符串是否可以被匹配,其中'.' 匹配任意单个字符;'*' 匹配零个或多个前面的那一个元素。

​ 输入是一个待匹配字符串和一个用字符串表示的正则表达式,输出是一个布尔值,表示是否可以匹配成功。

输入:s = “aab” p = “cab”
输出:true
解释:因为 ‘*’ 表示零个或多个,这里 ‘c’ 为 0 个, ‘a’ 被重复一次。因此可以匹配字符串 “aab”。

解析:

​ 两个字符串进行匹配,且字符具有特殊含义,本题动态规划的特点在于多种情况下的不同状态转移方程。

​ 设置状态:使用一个布尔类型的二维数组 dp,其中 dp[i][j] 表示以 i 截止的字符串是否可以被以 j 截止的正则表达式匹配。

​ 状态转移方程:根据正则表达式的不同情况,即字符、星号,点号,分情况讨论来更新 dp 数组

  • 相等情况:即s[i-1] == p[j-1] || p[j-1] == '.'的情况,这种情况下dp[i][j] == dp[i-1][j-1],即与前 i-1 个子串匹配情况一致

  • 不相等情况:

    • s[i-1] != p[j-1] && p[j-1] != '*' ,直接不匹配情况dp[i][j] = false
    • p[j-1] == '*':如果匹配0次则dp[i][j-2] == truedp[i][j] = true;如果匹配多次则需要验证'*'前一个字符相等情况即s[i-1] == p[j-1] || p[j-1] == '.',相等则dp[i][j] = dp[i-1][j],否则不匹配

​ 初始情况:空串匹配非空正则表达式,dp[0][0]=true,当p[j-1]=='*'是可以匹配0次匹配空串即dp[0][j] =dp[0][j-2];非空串匹配空正则表达式,dp[0][0]=true,其他情况都无法成功匹配。

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
        dp[0][0] = true;
        for(int j=1;j<=n;++j){
            if(p[j-1] == '*'){
                dp[0][j] = dp[0][j-2];
            }
        }

        for(int i=1;i<=m;++i){
            auto chs = s[i-1]; 
            for(int j=1;j<=n;++j){
                auto chp = p[j-1];
                if(chs == chp || chp == '.'){
                    dp[i][j] = dp[i-1][j-1];
                }else if(chp ==  '*'){
                    if(j>1){
                        if(dp[i][j-2]){
                            dp[i][j] = true;
                        }else{
                            auto prechp = p[j-2];
                            if(prechp==chs || prechp =='.'){
                                dp[i][j] = dp[i-1][j];
                            } 
                        }
                    }
                }
            }
        }
        return dp[m][n];
    }
};

参考资料

LeetCode 101:和你一起轻松刷题(C++) 第 7 章 深入浅出动态规划

  • 0
    点赞
  • 4
    收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:博客之星2021 设计师:Hiro_C 返回首页
评论

打赏作者

王清欢Randy

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

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值