• 字符串匹配DP问题合集


    字符串匹配DP

    详细题解见注释。

    LeetCode 10. 正则表达式匹配

    给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配

    ‘.’ 匹配任意单个字符
    ‘*’ 匹配零个或多个前面的那一个元素
    所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

    bool isMatch(string s, string p) 
    {
        int sl = s.size();
        int pl = p.size();
        // dp[i][j]表示s的前i个字符匹配p的前j个字符
        vector> dp(sl + 1, vector(pl + 1));
        for (int i = 0; i <= sl; ++i)
        {
            for (int j = 0; j <= pl; ++j)
            {
                if (j == 0)
                {
                    dp[i][j] = i == 0; // p的前0个字符只能匹配s的前0个字符
                    continue;
                }
                if (i >= 1 && (s[i - 1] == p[j - 1] || p[j - 1] == '.'))
                    dp[i][j] = dp[i - 1][j - 1]; // s的第i个字符与p的第j个匹配,因此整体能否匹配取决于s的前i-1个和p的前j-1个
                else if (p[j - 1] == '*')
                {
                    // 使用这个*,那么必须保证p的第j-1个字符能够匹配s的第i个字符
                    // 如果能匹配,则整体能否匹配取决于s的前i-1个字符能否与p的前j个字符匹配
                    if (i >= 1 && (p[j - 2] == s[i - 1] || p[j - 2] == '.'))
                        dp[i][j] = dp[i - 1][j]; 
                    // 也可以不使用这个*,即匹配0个*前面的那个字符
                    // 则整体能否匹配取决于p的前j-2个字符能否匹配s的前i个字符
                    dp[i][j] = dp[i][j] || dp[i][j - 2];
                }
            }
        }
        return dp[sl][pl];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    LeetCode 44. 通配符匹配

    给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。

    ‘?’ 可以匹配任何单个字符。
    ‘*’ 可以匹配任意字符串(包括空字符串)。
    两个字符串完全匹配才算匹配成功。

    说明:

    s 可能为空,且只包含从 a-z 的小写字母。
    p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。

    bool isMatch(string s, string p) 
    {
        int sl = s.size();
        int pl = p.size();
        vector> dp(sl + 1, vector(pl + 1));
        for (int i = 1; i <= sl; ++i)
        {
            if (p[i - 1] != '*')
                break;
            dp[0][i] = true;
        }
        // dp[i][j]表示p的前j个能否匹配s的前i个字符
        for (int i = 0; i <= sl; ++i)
        {
            for (int j = 0; j <= pl; ++j)
            {
                if (j == 0)
                {
                    dp[i][j] = i == 0; // p的前0个字符只能匹配s的前0个字符
                    continue;
                }
                if (i >= 1 && (p[j - 1] == s[i - 1] || p[j - 1] == '?'))
                {
                    dp[i][j] = dp[i - 1][j - 1];
                }
                else if (p[j - 1] == '*')
                {
                    // 让它匹配空子串,则dp[i][j]为真取决于p的前j-1个字符能否匹配s的前i个
                    dp[i][j] = dp[i][j - 1];
                    // 让它匹配当前字符,则dp[i][j]为真取决于p的前j个字符能匹配s的前i-1个
                    if (i >= 1)
                        dp[i][j] = dp[i][j] || dp[i - 1][j];
                }
            }
        }
    
        return dp[sl][pl];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    LeetCode 72. 编辑距离

    给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。

    你可以对一个单词进行如下三种操作:

    插入一个字符
    删除一个字符
    替换一个字符

    class Solution {
    public:
        int minDistance(string word1, string word2) 
        {
            typedef unsigned long long ull;
            int n1 = word1.size();
            int n2 = word2.size();
            vector> dp(n1 + 1, vector(n2 + 1));
    
            // dp[i][j]表示word1的前i个字符转换成word2的前j个字符需要的最少操作数
            for (int i = 0; i <= n1; ++i)
            {
                for (int j = 0; j <= n2; ++j)
                {
                    if (i == 0)
                        dp[i][j] = j; // 用word1的前0个字符转换成word2的前j个,只能插入j个字符
                    else if (j == 0)
                        dp[i][j] = i; // 用word1的前i个字符转换成word2的前0个,只能删除i个字符
                    else
                    {
                        // word1的第i个字符与word2的第j个字符相等,则本轮无需操作,操作数依赖于word1的前i-1个匹配word2的前j-1个
                        // word1的第i个字符与word2的第j个字符不相等,则本轮可以选择三种操作:
                        // 1、如果删除第i个字符,则dp[i][j]=dp[i-1][j]+1,即只能用word1的前i-1个字符转换成word2,同时操作数+1(因为进行了删除)
                        // 2、如果替换第i个字符,则dp[i][j]=dp[i-1][j-1]+1,即转换成了word1的第i个字符与word2的第j个字符相等的情况,只是操作数要+1(因为进行了替换)
                        // 3、如果插入第i个字符,则dp[i][j]=dp[i][j-1]+1,即使用word1的前i个字符匹配word2的前j-1个,同时插入一个字符匹配word2的第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], min(dp[i - 1][j - 1], dp[i][j - 1])) + 1;
                    }
                }
            }
    
            return dp[n1][n2];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    LeetCode 115. 不同的子序列

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

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

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

    class Solution {
    public:
        int numDistinct(string s, string t) 
        {
            int sl = s.size();
            int tl = t.size();
            // dp[i][j]表示用s的前i个字符匹配t的前j个字符有多少个不同的子序列
            vector> dp(sl + 1, vector(tl + 1));
            // 初始化dp数组
            for (int i = 0; i <= sl; ++i)
            {
                dp[i][0] = 1; // s的前i个字符匹配t的前0个,各有1种方案
            }
            for (int i = 1; i <= sl; ++i)
            {
                for (int j = 1; j <= tl; ++j)
                {
                    // s的第i个字符与t的第j个字符不相等,则只能用s的前i-1个字符匹配t的前j个
                    // s的第i个字符与t的第j个字符相等,则可以选择是否使用该字符
                    // 如果使用,则当前方案数基于s的前i-1个字符匹配t的前j-1个
                    // 如果不使用,则方案数基于s的前i-1个字符匹配t的前j个
                    if (s[i - 1] != t[j - 1])
                        dp[i][j] = dp[i - 1][j];
                    else 
                        dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
    
                }
            }
            return dp[sl][tl];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
  • 相关阅读:
    云原生 | Docker - [简介、架构、安装、加速]
    「动态规划」如何求乘积最大子数组?
    第一次接触web前端开发
    C++--vector的使用和模拟实现
    SpringACK对RabbitMQ消息的确认(消费)
    浅谈HTTP缓存与CDN缓存的那点事
    【无标题】
    Wireshark 4.2.5:发现 QUIC 和 VXLAN 协议的新功能
    基于虚拟机源码分析move合约(零):Move虚拟机执行原理
    Spring Task
  • 原文地址:https://blog.csdn.net/Wyf_Fj/article/details/126808991