力扣两道相似的正则匹配问题

517 阅读5分钟

正好在力扣看到两个比较相似的问题

两道题目都是关于实现通配符或者类似正则的算法,他们都可以使用动态规划的算法来求解。

题目的主要难度在与状态转移方程会有很多种情况,而且在匹配*时还可以通过保持*的匹配位置不变,移动字符串匹配的位置来实现

44. 通配符匹配

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

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

说明:

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

示例 1:

输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入: s = "aa" p = "" 输出: true 解释: '' 可以匹配任意字符串。

示例 3:

输入: s = "cb" p = "?a" 输出: false 解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。

示例 4:

输入: s = "adceb" p = "ab" 输出: true 解释: 第一个 '' 可以匹配空字符串, 第二个 '' 可以匹配字符串 "dce".

示例 5:

输入: s = "acdcb" p = "a*c?b" 输出: false

解法:动态规划

dp[i][j]代表 s[:i]p[:j](包含s[i]p[j])的匹配情况 ,于是分为以下三种情况

  1. p[j] != '*'

    此时的dp[i][j] 取决于s[i]p[j]是否能匹配(相等s[i] == p[j] 或者 p[j] == '?'

    1. 如果相等则dp[i][j] = dp[i-1][j-1]
    2. 不相等则dp[i][j]= false
  2. p[j] == '*'

    如果 *匹配的是空字符串,则dp[i][j] = dp[i][j-1]

    如果* 参与了匹配,则 dp[i][j] = dp[i-1][j] 可以理解成·s[i]·已经符合条件,进行了剔除,所以继续拿s[i-1]*匹配

    所以总的dp[i][j] = dp[i][j-1] || dp[i-1][j]

关于边界

我们可以改变dp[i][j]成:dp[i][j]代表 s[:i]p[:j](不包含s[i]p[j])能否匹配,dp[0][0]就代表两个字符串都为空的情况,于是对于所有s,p的判断都变成了s[i-1], p[j-1]

关于初始值

dp[0][0]=true s 和模式 p 均为空时,匹配成功;

dp[i][0]=false,即空模式无法匹配非空字符串;

dp[0][j] 需要分情况讨论:因为星号才能匹配空字符串,

所以只有当模式 p 的前j 个字符均为星号时,dp[0][j] 才为真。

代码

class Solution
{
public:
    bool isMatch(string s, string p)
    {
        int len_s = s.size(), len_p = p.size();
        vector<vector<bool>> dp(len_s + 1, vector<bool>(len_p + 1, false));

        // dp[0][0]=true s 和模式 p 均为空时,匹配成功;
        // dp[i][0]=false,即空模式无法匹配非空字符串;
        // dp[0][j] 需要分情况讨论:因为星号才能匹配空字符串,
        //  所以只有当模式 p 的前 j 个字符均为星号时,dp[0][j] 才为真。
        dp[0][0] = true;
        for (int j = 0; j < len_p; j++)
        {
            if (p[j] == '*')
            {
                dp[0][j + 1] = true;
            }
            else
            {
                break;
            }
        }

        auto match = [s, p](int i, int j) -> bool {
            if (p[j] == '?')
            {
                return true;
            }

            return p[j] == s[i];
        };

        for (int i = 1; i <= len_s; i++)
        {
            for (int j = 1; j <= len_p; j++)
            {
                if (p[j - 1] != '*')
                {
                    dp[i][j] = match(i - 1, j - 1) ? dp[i - 1][j - 1] : false;
                }
                else
                {
                    dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
                }
            }
        }

        return dp[len_s][len_p];
    }
};

10. 正则表达式匹配

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

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

说明:

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

示例 1:

输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入: s = "aa" p = "a*" 输出: true 解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:

输入: s = "ab" p = "." 输出: true 解释: "." 表示可匹配零个或多个('*')任意字符('.')。

示例 4:

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

示例 5:

输入: s = "mississippi" p = "misisp*." 输出: false

解法:动态规划

这道题和上题的唯一区别是,此题要求的*是和前一个字符相关。所以整体的思路差不多

dp[i][j]代表 s[:i]p[:j](不包含s[i]p[j])的匹配情况 ,于是分为以下四种情况

  1. p[j] != '*'

    此时的dp[i][j] 取决于s[i]和p[j]是否能匹配(相等s[i] == p[j] 或者 p[j] == '.'

    1. 如果相等则dp[i][j] = dp[i-1][j-1]
    2. 不相等则dp[i][j]= false
  2. p[j] == '*'

    如果 *匹配的是空字符串,则dp[i][j] = dp[i][j-2],注意此时是j-2因为空字符意味着***和它前面的字符**都不参与

    如果* 参与了匹配,则 dp[i][j] = dp[i-1][j] 可以理解成s[i]已经符合条件,进行了剔除,所以继续拿s[i-1]*匹配

    所以总的dp[i][j] = dp[i][j-2] || dp[i-1][j]

关于初始值

dp[0][0]=true s 和模式 p 均为空时,匹配成功;

dp[i][0]=false,即空模式无法匹配非空字符串;

dp[0][j] 根据实际计算了;

代码

class Solution {
public:
    bool isMatch(string s, string p) {
        int x = s.size();
        int y = p.size();

        vector<vector<bool>> dp(x+1, vector<bool>(y+1,false));
        dp[0][0] = true;

        auto m = [&](int i, int j) {
            if (i == 0) {
                return false;
            }
            if (p[j - 1] == '.') {
                return true;
            }
            return s[i - 1] == p[j - 1];
        };

        for (int i = 0; i <= x; ++i) {
            for (int j = 1; j <= y; ++j) {
                bool t = true;

                if (p[j - 1] == '*') {
                    if (m(i, j - 1)) {
                        t = dp[i - 1][j] || dp[i][j-2];
                    }else{
                        t = dp[i][j - 2];
                    }
                }
                else {
                    if (m(i, j)) {
                        t = dp[i - 1][j - 1];
                    }else{
                        t = false;
                    }
                }

                dp[i][j] = t;
            }
        }

        return dp[x][y];
    }
};