【LeetCode刷题记录】10. 正则表达式匹配

题目描述:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
题解:
一、回溯
检查时,如果只考虑’.’,显然只需要同时在s和p中越过当前元素就可以了。
对于’*’,要分两种情况考虑:1、表示0个前面那个元素,这时在p中越过它和它前面那个元素,s不变;2、表示1个前面那个元素(可以扩展到n个),这时在s中越过当前元素,p不变。
引用官方解答的一句话:“当模式串中有星号时,我们需要检查匹配串 s 中的不同后缀,以判断它们是否能匹配模式串剩余的部分。一个直观的解法就是用回溯的方法来体现这种关系。”
递归实现:

bool isMatch(string s, string p) {
 if (p.empty())return s.empty();
 if ('*' == p[1])
  return isMatch(s, p.substr(2)) || (!s.empty() && (s[0] == p[0] || '.' == p[0]) && isMatch(s.substr(1), p));
 else
  return !s.empty() && (s[0] == p[0] || '.' == p[0]) && (isMatch(s.substr(1), p.substr(1)));
}

复杂度分析:用T和P分别表示匹配串和模式串的长度,时间复杂度:O((T+P)2(T+P/2));空间复杂度:O((T+P)2(T+P/2))。
二、动态规划
着重解释DP,由简单到复杂。
我们用dp[i][j]表示s的前i个能否被p的前j个匹配;首先,只考虑’.’,很容易得到这样的状态转移方程:

if(s[i] == p[j] || '.' == p[j]) dp[i][j] = dp[i-1][j-1];

在此基础上,考虑这个问题:

s = "abc";
p = "a.c";

首先,定义dp数组,显然,因为有初始状态的存在,dp的大小应为(s.size()+1)*(p.size()+1):
在这里插入图片描述
初始化dp[0][0],即s、p都取空,显然dp[0][0] = true;初始化dp[0][j],1<=j<=3,显然,均为false;初始化dp[i][0],1<=i<=3,显然,均为false。
在这里插入图片描述
接着,应用状态转移方程,因为i=0,j=0,s[0]==p[0],所以dp[1][1]=dp[0][0]=true,注意这里dp数组的下标;
i=0,j=1,s[0]!=p[1],’.’==p[1],所以dp[1][2]=dp[0][1]=false;继续循环,逐次填充dp数组。
在这里插入图片描述
注意:程序实现时,在定义dp数组时,所有元素初始化为false。
令m=s.size(),n=p.size();显然,dp[m][n]=dp[3][3]=true即为结果。
将上述过程应用于:

s = "abc";
p = ".c";

得到:
在这里插入图片描述
显然,dp[m][n]=dp[3][2]=false即为结果。
只考虑’.’,代码:

bool isMatchOnly(string s, string p) {
 if (p.empty()) return s.empty();
 int m = int(s.size());
 int n = int(p.size());
 vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
 dp[0][0] = true;
 for (int i = 0; i < m; i++) {
  for (int j = 0; j < n; j++) {
   if (s[i] == p[j] || '.' == p[j])
    dp[i + 1][j + 1] = dp[i][j];
  }
 }
 return dp[m][n];
}

以上为只考虑’.‘的情况,应用动态规划看起来有点多余,但是一旦’*‘和’.'同时考虑,问题就复杂的多,动态规划的意义就体现出来了。
下面开始同时考虑:

if(s[i] == p[j] || '.' == p[j]) dp[i][j] = dp[i-1][j-1]; //'.' 显然,依旧成立。
if('*' == p[j]){ //'*'
 //case 1:'*'的上一个字符!=s[i]与'.',显然这种情况下'*'只能代表0个上一个字符。

  //例如s="ab",p="ac*b",则有:
  //i=0,j=2时,检查"a"能否被"ac*"匹配:
  //显然,此时*代表0个上一个字符,这种情况下:dp[i][j] = dp[i][j-2];
  //即"a","ac*"->(状态转移至)"a","a";
  
  //状态转移方程:
 if(s[i] != p[j-1] && '.' != p[j-1]) dp[i][j] = dp[i][j-2]; 
 
 //case 2:'*'的上一个字符==s[i]或'.':共有3种情况:'*'代表0,1,多个上一个字符
 //以下三种情况都以s="abbbbbc",p="abb*bc"为例分析:
 
  //case 2.1:i=1,j=3时,检查"ab"能否被"abb*"匹配:
  //显然,此时'*'代表0个上一个字符,这种情况下:dp[i][j] = dp[i][j-2];
  //即"ab","abb*"->(状态转移至)"ab","ab"
  
  //case 2.2:i=2,j=3时,检查"abb"能否被"abb*"匹配:
  //显然,此时'*'代表1个上一个字符,这种情况下:dp[i][j] = dp[i][j-1];
  //即"abb","abb*"->(状态转移至)"abb","abb"
  
  //case 2.3:i=3,j=3时,检查"abbb"能否被"abb*"匹配:
  //显然,此时'*'代表2(多)个上一个字符,这种情况下:dp[i][j] = dp[i-1][j];
  //即"abbb","abb*"->(状态转移至)"abb","abb*",
  
  //这里观察到,case 2.3转移后的状态,刚好是case 2.2转移前的状态,
  //再次应用case 2.3的结论,"abb","abb*"->(状态转移至)"ab","abb*"
  //又刚好是case 2.1转移前的状态!
  //这说明,如果'*'代表n个上一个字符,我们通过在待配串中减少末尾的一个字符(i-1),
  //问题就归结(状态转移)为'*'代表n=n-1个上一个字符,直至n=0,归结为"0"(case 1或case 2.1);
  //即,case 2.2是冗余的!
  
  //注意:进入case 2的条件是:'*' == p[j] && (s[i] == p[j-1] || '.' == p[j-1])
  //那么我们执行case 2.1,2.3中的哪一个呢?答案是:都执行,结果取或。
  //why?因为'*'代表0个或多个前一个元素,
  //执行case 2.1就相当于我们去验证“'*'代表0个前一个元素”这个命题对不对;
  //执行case 2.3就相当于我们去验证“'*'代表多个前一个元素”这个命题对不对;
  //显然,这两个命题在dp的相同位置,有且仅有一个为真(即'*'只能有一种含义)。
  //我们把这两者的结果取或,即可保证真命题的结果的有效性(因为另一个假命题的结果一定是false)。
  
  //状态转移方程:
 if(s[i] == p[j-1] || '.' == p[j-1]) 
  dp[i][j] = dp[i][j-2] /*|| dp[i][j-1]*/|| dp[i-1][j];
}

分析完毕,实战:

s = "abc";
p = "a.*";

初始化dp[0][0],即s、p都取空,显然dp[0][0] = true;初始化dp[0][j],1<=j<=3,显然,均为false;初始化dp[i][0],1<=i<=3,显然,均为false。
注意:假如s为空,p="a*"及类似组合,那么dp[0][j]会发生变化,需另做处理。
在这里插入图片描述
注意一点:p[0]不会为星号,因为没意义,不需要考虑;即填写dp[i][1]时,不会出现访问越界的情况。
继续填写dp数组:
在这里插入图片描述
显然,dp[m][n]=dp[3][3]=true即为结果。
完整代码:

bool isMatchDP(string s, string p) {
 if (p.empty()) return s.empty();
 int m = int(s.size()), n = int(p.size());
 vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
 dp[0][0] = true;
 //init: s="",p="a*"or".*"or...
 for (int j = 1; j < n; j++){
  if ('*' == p[j] && dp[0][j - 1] == true)
   dp[0][j + 1] = true;
 }
 for (int i = 0; i < m; i++) {
  for (int j = 0; j < n; j++) {
   if (s[i] == p[j] || '.' == p[j])
    dp[i + 1][j + 1] = dp[i][j];
   if ('*' == p[j]) {
    if (s[i] != p[j - 1] && '.' != p[j - 1])
     dp[i + 1][j + 1] = dp[i + 1][j - 1];
    else
     dp[i + 1][j + 1] = dp[i + 1][j - 1]/* || dp[i + 1][j] */|| dp[i][j + 1];
   }
  }
 }
 return dp[m][n];
}

复杂度分析:两层循环,时间复杂度:O(mn);创建了二维dp数组,空间复杂度:O(mn)。

原创文章 23 获赞 0 访问量 984

猜你喜欢

转载自blog.csdn.net/weixin_42192493/article/details/104796053