算法刷题笔记 - 正则表达式匹配(动态规划)

正则表达式匹配

题目描述

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

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

例子:

  • "mississippi"和"mis*is*p*."不匹配
  • "aab"和"c*a*b"匹配
  • 特别的".*"可匹配任意字符串
解法构思

有关串匹配应想到利用动态规划

  • 此题中的p和s没有位置对应关系, 因为p的组成元素可能一个字符, 可能是一个字符加上’*’, 为了使p和s有一定的位置对应关系, 即是p中的每一个元素占一个位置, 即令p_为p忽略’*'的序列, 另外构建star表, 表示每个位置的字符是否可重复,可为1,反之为0。接下来,就是解决 s和p_ 的匹配问题,与以前的通配符匹配的那道题极其类似。

  • 定义规划表
    首先确定应是二维表,又考虑到空串的匹配,行列数应该是字符串s和匹配串p_的长度加一,即定义DP[0:m+1][0:n+1]DP[i][j] 代表s的前i个字符构成的串与p_的前j个字符构成的串是否匹配, 匹配为1, 不匹配为0

  • 构建转移方程
    考虑DP[i][j]

    • 定义:
      • 当前匹配,是s[0:i - 1](包含s[i - 1]字符)与p_[0:j - 1](包含p_[i - 1]字符)的匹配,
      • s的当前字符,是s[i-1],
      • p_的当前字符,是p_[j-1],
      • 字符的“重复",是指 "字符*"
    • 如果star[j - 1] == 0,即匹配串当前字符没有“可重复”(即星号)标记,
      • 如果s[i - 1] == p_[j - 1],应DP[i][j] = DP[i - 1][j - 1],即字符串与匹配串的尾字符相等, 是否匹配即取决于二者除去尾字符后的匹配情况。
      • 如果p_[j - 1] == ‘.’, 应DP[i][j] = DP[i - 1][j - 1],匹配串的尾字符为’.’, 即对字符串尾字符没要求, 即是否匹配即取决于二者除去尾字符后的匹配情况。
    • 如果star[j - 1] == 1,即匹配串当前字符有“可重复”(即星号)标记,
      • 如果p_[j - 1] == s[i - 1] 或者 p_[j - 1] == ‘.’, 应DP[i][j] = DP[i][j - 1] || DP[i - 1][j] ,则说明当前字符相匹配,则该考虑的是,p_的当前字符的“重复”匹配的是一个字符还是多个字符。
        1. 如果此“重复”仅匹配一个字符, 则当此时 s 能与 p_除去尾字符 后匹配时当前匹配成功;
        2. 如果此“重复”匹配多个字符,即当 s除去最后字符 后仍能与 p_[0:j - 1] 匹配时当前匹配成功。
      • 如果p_[j - 1] != s[i - 1]并且匹配串当前字符(p_[j - 1])不是’.’,应DP[i][j] = DP[i][j - 1],,即匹配串当前字符的 “重复” 对应空串(即’*'代表的重复0次的情况),即当此时 s 能与 p_除去尾字符 后匹配时当前匹配成功;
    • 其他情况 : DP[i][j] = 0
  • 考虑边界情况

    • 两空串一定可匹配, 即DP[0][0] = 1
    • 一个空的匹配串不能匹配任何串, 即DP[i][0] = 0, i = 1, 2, … ,len(s)
    • 一个空串只能被全部由“重复”构成的匹配串匹配, 即DP[0][j] = 1, j = 1, 2, …, p_的全部由“重复”构成的最大前缀的长度
上手编程
bool isMatch(string s, string p) {
   const int M = s.size();   
   
   string p_;  // 去除'*'后的p
   vector<char> star;  // 是否重复

   p.push_back('$');  // 增加哨兵
   
   //  构建p_
   for (int i = 0; i < p.size() - 1; ++i) {
       p_.push_back(p[i]);
       if (p[i + 1] == '*') {
           ++i;
           star.push_back(1);
       } else star.push_back(0);
   }
   
   const int N = p_.size();
   
   // 定义DP表
   vector<vector<char>> dp(M + 1, vector<char>(N + 1, 0));
   
   // 处理边界情况
   dp[0][0] = 1;
   for (int i = 1; i <= N && star[i - 1] == 1; ++i)
       dp[0][i] = 1;
   
   // 构建DP表
   for (int i = 1; i <= M; ++i) {
       for (int j = 1; j <= N; ++j) {
       
           if (star[j - 1] == 0) {
               if (p_[j - 1] == '.' || p_[j - 1] == s[i - 1]) dp[i][j] = dp[i - 1][j - 1];
               
           } else {
               if (p_[j - 1] == '.'|| p_[j - 1] == s[i - 1])
                   dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
               else dp[i][j] = dp[i][j - 1];
           }
       }
   }
   
   //  dp[M][N]即为结果
   return dp.back().back();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值