LeetCode_0044. 通配符匹配,LeetCode_0010. 正则表达式匹配,带字母'*''?'的模式串匹配仅带字母的主串

LeetCode_0044. 通配符匹配

  给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?' 和 '*' 匹配规则的通配符匹配:

  1. '?' 可以匹配任何单个字符。
  2. '*' 可以匹配任意字符序列(包括空字符序列)。
  3. 判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

  • 示例 1:

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

  • 示例 2:

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

  • 示例 3:

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

  • 提示:

    0 <= s.length, p.length <= 2000
    s 仅由小写英文字母组成
    p 仅由小写英文字母、'?' 或 '*' 组成
    

分析

状态s[0..j] & p[0..i] 的匹配情况可以推出下一状态 s[0..j+1] & p[0..i/i+1] 的匹配情况。

存在'*'匹配0个或多个,因此状态推导关系不仅仅是一维的。

问题可以分解为若干 重叠子问题 :二者的子串匹配 --> 子问题之间存在 二维的状态推导关系 --> 二维动态规划

dp数组

设dp[i][j]表示s[0..j-1] & p[0..i-1]的匹配情况,true/false。

初始化

dp的初始化需要考虑空串 ,s为空 or p为空。

  • i = 0 时,p[0..i-1]为空串,仅能与空串(j = 0)匹配成功
    dp[0][0] = true;

  • j = 0 时,s[0..j-1]为空串,仅能与空串(i = 0)或'...'匹配成功
    当p[0..i-1]是连续的'*'时,dp[i][0] = true;

状态推导

i = [1..p.length()],遍历p[0..p.length()-1]逐行推导:

  • 若p[i-1] == '*',可不匹配字符,或匹配字符
    dp[i][j] = dp[i-1][j] || dp[i][j-1]

  • 若p[i-1] == '?',匹配字符
    dp[i][j] = dp[i-1][j-1]

  • 若p[i-1] == 字母,与s[j-1]比较,相等就匹配成功,不等就匹配失败
    dp[i][j] = dp[i-1][j-1] && p[i-1] == s[j-1]

实现

dp数组的推导公式可知,dp[i]仅与dp[i-1]有关,可以压缩二维dp数组为一维,注意j=0时的dp值

  class Solution {
  public:
      bool isMatch(string s, string p) {
          const int sLen = s.length(), pLen = p.length();

          vector<bool> dpLine(sLen + 1, false);
          dpLine[0] = true;

          int dpZeroTrue = 1;
          for(; dpZeroTrue <= pLen && p[dpZeroTrue - 1] == '*'; ++dpZeroTrue);

          // 字符串i-1对应dpi,字符串j-1对应dpj
          for(int i = 1; i <= pLen; ++i) {
              const char pi = p[i - 1];
              if(pi == '*') {
                  dpLine[0] = i < dpZeroTrue;
                  for(int j = 1; j <= sLen; ++j) {
                      dpLine[j] = dpLine[j - 1] || dpLine[j];
                  }
              } else if(pi == '?'){
                  for(int j = sLen; j > 0; --j) {
                      dpLine[j] = dpLine[j - 1];
                  }
                  dpLine[0] = i < dpZeroTrue;
              } else {
                  for(int j = sLen; j > 0; --j) {
                      dpLine[j] = dpLine[j - 1] && pi == s[j - 1];
                  }
                  dpLine[0] = i < dpZeroTrue;
              }
          }

          return dpLine.back();
      }
  };

LeetCode_0010. 正则表达式匹配

  给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '*' 和 '.' 匹配规则的通配符匹配:

  1. '*' 可以匹配0个或多个前序字符。
  2. '.' 可以匹配任意字符。
  3. 判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

分析

dp数组

同上,设dp[i][j]表示s[0..j-1] & p[0..i-1]的匹配情况,true/false。

初始化

  • i = 0时,同上,p[0..i-1]为空串,仅能匹配空串。
    dp[0][0] = true
  • j = 0时,s[0..j-1]为空串,考虑'' 的匹配情况。
    若p[0..i-1]是连续的"x
    "组合,则在'*'的位置dp值true。

状态推导

i = [1..p.length()],遍历p[0..p.length()-1]逐行推导:

  • 若p[i-1] == '*',可不匹配字符,或匹配前序字符
    dp[i][j] = dp[i-2][j] || (dp[i][j-2] && (p[i-2] == s[j-1] || p[i-2] == '.'))

  • 若p[i-1] == '.',匹配字符
    dp[i][j] = dp[i-1][j-1]

  • 若p[i-1] == 字母,与s[j-1]比较,相等就匹配成功,不等就匹配失败
    dp[i][j] = dp[i-1][j-1] && p[i-1] == s[j-1]

实现

dp数组的推导公式可知,dp[i]仅与dp[i-1]有关,可以压缩二维dp数组为一维,注意j=0时的dp值

 bool isMatch(string s, string p) {
    const int sLen = s.length(), pLen = p.length();
    vector<vector<bool>> dp(pLen + 1, vector<bool>(sLen + 1, false));

    dp[0][0] = true;
    for(int i = 2; i <= pLen; i += 2) {
        if(p[i - 1] == '*') {
            dp[i][0] = true;
        } else {
            break;
        }
    }

    for(int i = 1; i <= pLen; ++i) {
        const char pi = p[i - 1];
        if(pi == '*') {
            const char pre = p[i - 2];
            for(int j = 1; j <= sLen; ++j) {
                dp[i][j] = dp[i - 2][j] || (dp[i][j - 1] && (pre == s[j - 1] || pre == '.'));
            }
        } else if(pi == '.') {
            for(int j = 1; j <= sLen; ++j) {
                dp[i][j] = dp[i - 1][j - 1];
            }
        } else {
            for(int j = 1; j <= sLen; ++j) {
                dp[i][j] = dp[i - 1][j - 1] && pi == s[j - 1];
            }
        }
    }

    return dp[pLen][sLen];
}

进阶

类似的正则表达式题目还有很多:主串和模式串按照一定的规则完全匹配。按匹配规则,通配符能匹配可选数量的主串字符。

不同规则下,dp数组的初始化和推导公式有所不同。

题目描述

笔试中遇到一题,题目大致如下:

  给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?' 、 '*' 和 '.' 匹配规则的通配符匹配:

  1. '?' 可以匹配1个或多个前序字符。
  2. '*' 可以匹配0个或多个前序字符。
  3. '.' 可以匹配任意字符。
  4. 判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

dp数组

同上,设dp[i][j]表示s[0..j-1] & p[0..i-1]的匹配情况,true/false。

初始化

  • i = 0时,同上,p[0..i-1]为空串,仅能匹配空串。
    dp[0][0] = true
  • j = 0时,s[0..j-1]为空串.由于'?' 和 '' 的匹配情况与前序字符有关,所以要多加考虑。笔试是就是这里出现了疏漏。
    若p[0..i-1]是连续的"x
    "组合,则在'*'的位置dp值true。

状态推导

i = [1..p.length()],遍历p[0..p.length()-1]逐行推导:

  • 若p[i-1] == '*',可不匹配字符,或匹配前序字符
    dp[i][j] = dp[i-2][j] || (dp[i][j-2] && (p[i-2] == s[j-1] || p[i-2] == '.'))

  • 若p[i-1] == '?',可匹配前序字符
    dp[i][j] = dp[i][j-2] && (p[i-2] == s[j-1] || p[i-2] == '.')

  • 若p[i-1] == '.',匹配字符
    dp[i][j] = dp[i-1][j-1]

  • 若p[i-1] == 字母,与s[j-1]比较,相等就匹配成功,不等就匹配失败
    dp[i][j] = dp[i-1][j-1] && p[i-1] == s[j-1]

posted @ 2024-09-15 11:47  某糕  阅读(8)  评论(0编辑  收藏  举报