剑指offer——正则表达式匹配

继续字符串类型的题目,原题目链接:正则表达式匹配

为方便直接观看,还是先抄一下题目。

题目描述:

请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配。

题目分析:

首先这个题目读下来,就感觉有点复杂。

大致是要分为两类,一类是字符串,然后一类是模式;模式中需要包含特殊的字符'.'或'*',或许也可以不包含;

这两类是否匹配,应该是指字符串能否转化为该模式,或者是一个模式能不能写成该字符串的形式

比较所给的例子:

首先是匹配的两种情况,分别如下图所示:

1)字符串"aaa"与模式"a.a"

第1个字符均是'a';第2个字符分别是'a'与'.';由于'.'可以表示任意一个字符,所以匹配;第3个字符也均为'a'。所以两者匹配

2)字符串"aaa"与模式"ab*ac*a"

第1个字符均是'a';第2个字符是'a'与‘b',但是'b'后面有'*',所以字符串中可以没有'b',在接下来模式中是'a',匹配;第3个字符也是'a',模式中出现了'c',又出现了'*',所以字符串中没出现'c'也可以,后面是'a',匹配,再之后,字符串结束,模式也结束,所以两者匹配

然后是不匹配的两种情况,也如下图所示:

3)字符串"aaa"与模式"aa.a"

很明显。模式中前面3个字符均能正确匹配,但最后一个无对应的字符与之匹配;所以两者不匹配。

4)字符串"aaa"与模式"ab*a"

字符串中最后一个字符'a'没有元素与之匹配,所以两者也不匹配。

 

比较所给例子的过程中,发现判断的步骤如下:(有一点需要注意:'*'表示的是它前面的一个字符可以出现任意多次)

(a)挨个取出字符串中的元素与模式中的元素,进行比较,若两者相同(此处需注意)先要判断模式中下一个字符是否为'*';若是,则比较棘手;若不是,继续进行下一个元素的比较

(b)如果不同,则判断模式中的字符是否为'.'若是,则继续进行下一个元素的比较;

(c)如果不是,则比较模式中的下一个字符是否为'*;若不是'*',则不匹配;结束比较

(d)若模式中下一个字符是'*',则比较模式中'*'的下个字符是否与字符串中刚才的元素相同

(e)反复进行上述比较,直到两者中有一方元素比较完毕,若另一方还存在字符,则不匹配。

由于'*'表示的是它前面的一个字符可以出现任意多次;所以比较棘手。主要包含以下3种情况:

1.'*'前面的字符出现0次,则需要字符串中仍然比较该元素,而模式中比较'*'之后的元素;

2.'*'前面的字符出现1次,则需要字符串中比较下一个元素,而模式中比较'*'之后的元素;

3.'*'前面的字符出现超过1次,则需要字符串中比较下一个元素而模式中仍然比较'*'之前的元素

由于不能确定具体选取哪种情况,所以一般需要采用递归来尝试。

 在用代码实现之前,首先再来分析一下空串的情况

1.当字符串与模式均为空时,可以成功匹配;

2.当字符串不为空,而模式为空时,不能匹配;

3.当字符串为空,而模式不为空时,则不一定,如果模式中每个字符后面都有一个'*',则也可以成功匹配;否则,不能匹配。

下面是上述过程的实现代码:

 1     public boolean match(char[] str, char[] pattern) {
 2         if(str == null || pattern == null){    //若有一个不存在,则不匹配
 3             return false;
 4         }
 5         int strLength = str.length;
 6         int patLength = pattern.length;
 7         if(strLength == 0 && patLength == 0){    //若两者均为空字符串,则匹配
 8             return true;
 9         }
10         if(patLength == 0){    //若仅模式为空字符串,则不匹配
11             return false;
12         }
13         if(strLength == 0){    //若仅字符串为空,则需要判断模式中每个非'*'后是否都有'*';若不是,则不匹配
14             for(int i=0; i<patLength; i++){
15                 if(pattern[i]!='*'){
16                     if(i+1 >= patLength || pattern[i+1]!='*'){
17                         return false;
18                     }
19                 }
20             }
21             return true;
22         }
23         //若两者均不为空,则比较两者的各个字符
24         return matchChar(str, 0, pattern, 0);
25         
26     }
27     public boolean matchChar(char[] str, int strIndex, char[] pattern, int patIndex){
28         //检查是否比较完所有的字符
29         if(strIndex == str.length && patIndex == pattern.length){    //若字符串与模式同时比较完毕
30             return true;
31         }
32         //若模式先比较完毕
33         if(strIndex != str.length && patIndex == pattern.length){
34             return false;
35         } 
36         //若均没有比较完毕
37         if(str[strIndex] == pattern[patIndex]){    //若字符串的元素等于模式的元素
38             if(patIndex+1 < pattern.length && pattern[patIndex+1] == '*'){    //判断模式中下一个元素是否为'*'
39                 return matchChar(str, strIndex, pattern, patIndex+2)
40                     || matchChar(str, strIndex+1, pattern, patIndex+2)
41                     || matchChar(str, strIndex+1, pattern, patIndex);
42             }else{    //若模式中下一个元素不是'*'
43                 //继续进行下一个元素的比较
44                 return matchChar(str, strIndex+1, pattern, patIndex+1);
45             }
46         }else{    //若字符串的元素不等于模式的元素
47             //判断模式中的字符是否为'.'
48             if(pattern[patIndex] == '.'){
49                 return matchChar(str, strIndex+1, pattern, patIndex+1);
50             }else{    //若模式中的字符不是'.',则还需要判断下一个字符是否为'*'
51                 if(patIndex+1 < pattern.length && pattern[patIndex+1] == '*'){
52                     //若是,则与模式中'*'后的元素进行比较
53                     return matchChar(str, strIndex, pattern, patIndex+2);
54                 }else{    //若不是,则不匹配
55                     return false;
56                 }
57             }
58         }
59     }
View Code

这是我按照上面的分析所写的,但是并没能成功通过牛客网上的所有测试用例。例如此例:"a"与".*";若按照上述匹配步骤,则'a'与'.'匹配,之后字符串没字符了,所以不匹配。

但实际上是可以成功匹配的;造成以上错误的原因是直接开始比较字符串中的元素与模式中的元素,如果在比较之前先判断模式中的下一个字符是否为'*',就可以避免这种错误了。

 

那么重新修正之后的匹配步骤如下

a)首先判断有空串的情况,这一步和上面的一样

b)然后是判断模式中的下一个字符是否存在并且为'*'

  若不为'*',则直接比较字符串中的当前元素与模式中的当前元素

    若当前元素相同,则继续比较下一个元素;

    若不同,则判断模式中当前元素是否为'.'

      若不是'.',则不匹配;若是,则继续比较下一个元素。

  若是'*',则先要比较两元素是否相同或者模式中元素为'.';

    若不是,则模式中当前元素出现0次;然后开始比较字符中当前元素与模式中'*'之后的元素

    若是,则模式中当前元素可能出现1次或多次,或者0次

      若出现1次,则开始比较字符串中下一个元素与模式中'*'之后的元素

      若出现不止1次,则开始比较字符串中下一个元素与模式中当前元素

      若出现0次,则开始比较字符中当前元素与模式中'*'之后的元素;

c)上面的比较需要递归进行,递归地结束条件有两个:

  1.上面比较过程中出现了不匹配的情况

  2.有一方比较完毕,如果字符串与模式同时比较完毕,则匹配

    若字符串没有比较完毕,而模式比较完毕,则不匹配

    若字符串比较完毕,而模式还没有,则继续比较

修正之后的代码如下所示:

 1     public boolean match(char[] str, char[] pattern) {
 2         if(str == null || pattern == null){    //若有一个不存在,则不匹配
 3             return false;
 4         }
 5         int strLength = str.length;
 6         int patLength = pattern.length;
 7         if(strLength == 0 && patLength == 0){    //若两者均为空字符串,则匹配
 8             return true;
 9         }
10         if(patLength == 0){    //若仅模式为空字符串,则不匹配
11             return false;
12         }
13         if(strLength == 0){
14             //若仅字符串为空,则需要判断模式中每个非'*'后是否都有'*';若不是,则不匹配
15             for(int i=0; i<patLength; i++){
16                 if(pattern[i]!='*'){
17                     if(i+1 >= patLength || pattern[i+1]!='*'){
18                         return false;
19                     }
20                 }
21             }
22             return true;
23         }
24         //若两者均不为空,则比较两者的各个字符
25         return matchChar(str, 0, pattern, 0);
26         
27     }
28     
29      public boolean matchChar(char[] str, int strIndex, char[] pattern, int patIndex){
30          //检查是否比较完所有的字符
31          if(strIndex == str.length && patIndex == pattern.length){
32              //若字符串与模式同时比较完毕,则匹配
33              return true;
34          }
35          //若模式先比较完毕,则不匹配
36          if(strIndex != str.length && patIndex == pattern.length){
37              return false;
38          }
39          //若均没有比较完毕
40          //判断模式中下一个元素是否为'*',
41          if(patIndex+1 < pattern.length && pattern[patIndex+1] == '*'){
42              //若是'*',则先要比较两元素是否相同或者模式中元素为'.'
43              if(strIndex != str.length && (str[strIndex] == pattern[patIndex] || pattern[patIndex] == '.')){
44                  //若相同或为'.',则字符串中元素可能出现1次或多次,也可能是0次
45                  return matchChar(str, strIndex+1, pattern, patIndex+2)
46                          || matchChar(str, strIndex+1, pattern, patIndex)
47                          || matchChar(str, strIndex, pattern, patIndex+2);
48              }else{
49                  return matchChar(str, strIndex, pattern, patIndex+2);
50              }
51          }
52          //若不是'*',则只需要比较两元素是否相同或者模式中元素为'.'
53          if(strIndex != str.length && (str[strIndex] == pattern[patIndex] || pattern[patIndex] == '.')){
54              //若是,则继续比较下一个
55              return matchChar(str, strIndex+1, pattern, patIndex+1);
56          }
57          return false;
58      }
View Code

此代码在牛客网上运行时间为22ms。

 

此题暂时还没找到什么简便的方法,一般都是直接分析,获取直接匹配的步骤;题目的主要难点在于匹配的步骤比较复杂,并且出现了分支。

然后解体的主要思路就是不要直接比较两元素,而是要先判断模式中下一个元素是否为'*';然后就是要使用递归来实现。

posted @ 2020-05-14 17:30  暮光乐鱼  阅读(178)  评论(0编辑  收藏  举报