Leetcode OJ: Wildcard Matching

Implement wildcard pattern matching with support for '?' and '*'.

'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false

是思路与正则匹配类似,个人感觉是比正则匹配那题要容易一些的,但一做才发现是个坑。

不过有了正则匹配那题当铺垫,还是能做出来的,LZ实在地把正则那题的思路搬过来(参考http://www.cnblogs.com/flowerkzj/p/3726667.html

稍微改改就提交了,当然也是先用递归的方案。以下代码通不过:

 1 class Solution {
 2 public:
 3     bool isMatch(const char *s, const char *p) {
 4         if (*p == 0) return *s == 0;
 5         if (*p != '*') {
 6             return (*p == *s || (*p == '?' && *s != 0)) && isMatch(s+1, p+1);
 7         }
 8         while (*s != 0) {
 9             if (isMatch(s, p+1))
10                 return true;
11             s++;
12         }
13         return isMatch(s, p+1);
14     }
15 };
View Code

果断TLE,提示错误样例是

Last executed input:"aaabbbaabaaaaababaabaaabbabbbbbbbbaabababbabbbaaaaba", "a*******b"

看到那些*于是就合并呗,又是一个通不过的代码:

 1 class Solution {
 2 public:
 3     bool isMatch(const char *s, const char *p) {
 4         if (*p == 0) return *s == 0;
 5         if (*p != '*') {
 6             return (*p == *s || (*p == '?' && *s != 0)) && isMatch(s+1, p+1);
 7         }
 8         while (p[1] == '*')
 9             ++p;
10         while (*s != 0) {
11             if (isMatch(s, p+1))
12                 return true;
13             ++s;
14         }
15         return isMatch(s, p+1);
16     }
17 };
View Code

还是TLE,提示错误样例是老长的一段s与p,好吧,只能出大杀器,动态规划了!

还是稍稍改改正则匹配那题的动态规划的代码,但还是过不了,再贴一个过不了的代码:

 1 class Solution {
 2 public:
 3     bool isMatchSingle(char s, char p) {
 4         return (s == p || p == '?');
 5     }
 6     bool isMatch(const char *s, const char *p) {
 7         int slen = strlen(s);
 8         int plen = strlen(p);
 9         
10         vector<int> dp1(slen + 1, false), dp2(slen + 1, false);
11         vector<int> *pre = &dp1, *cur = &dp2;
12         dp1[0] = true;
13         
14         while (*p != 0) {
15             cur->assign(slen + 1, false);
16             if (*p != '*') {
17                 for (int i = 0; i < slen; ++i) {
18                     (*cur)[i + 1] = ((*pre)[i] && isMatchSingle(s[i], p[0]));
19                 }
20             } else {
21                 (*cur)[0] = (*pre)[0];
22                 while (p[1] == '*')
23                     ++p;
24                 for (int i = 0; i < slen; ++i) {
25                     (*cur)[i + 1] = (*pre)[i + 1] || (*pre)[i] || (*cur)[i];
26                 }
27             }
28             ++p;
29             swap(cur, pre);
30         }
31         return (*pre)[slen];
32     }
33 };
View Code

还是TLE,提示错误样例是s是一个超长的a,p也是一个超长的a与*的结合。

这就让我要重新思考下了,通配符里没有像正则那样的a*这样的表达,要匹配多少个a就重复出现多少次!这样就造成p会特别长!

但我看这个样例的特点就是无限个重复,于是我就想到了是不是可以把p压缩一下,p的匹配单元不再是单个字符,按连续重复出现的子串为基本匹配单元。

比如说p = "*aaaabbb*",则匹配单元为{"*", 4个"a", 3个"b", "*"},为原来的9个匹配单元变成 了4个。于是按这思路把原来的改改,就有如下代码了。

 1 class Solution {
 2 public:
 3     bool isMatch(const char *s, const char *p) {
 4         int slen = strlen(s);
 5         int plen = strlen(p);
 6         
 7         // 记录当前字符及以后会连续出现多少次
 8         vector<int> scount(slen, 1);
 9         vector<int> pcount(plen, 1);
10         
11         for (int i = slen - 2; i >= 0; --i) {
12             if (s[i] == s[i + 1])
13                 scount[i] = scount[i + 1] + 1;
14         }
15         
16         for (int i = plen - 2; i >= 0; --i) {
17             if (p[i] == p[i + 1])
18                 pcount[i] = pcount[i + 1] + 1;
19         }
20         
21         
22         vector<int> dp1(slen + 1, false), dp2(slen + 1, false);
23         vector<int> *pre = &dp1, *cur = &dp2;
24         dp1[0] = true;
25         int pi = 0;
26         while (p[pi] != 0) {
27             cur->assign(slen + 1, false);
28             if (p[pi] != '*') {
29                 // 这里更新的都是(*cur)[i + pcount[pi]]的值
30                 if (p[pi] != '?') { // 判断是否连续匹配pcount[pi]个p[pi]
31                     for (int i = 0; i + pcount[pi] <= slen; ++i) {
32                         (*cur)[i + pcount[pi]] = (*pre)[i] && p[pi] == s[i] && scount[i] >= pcount[pi];
33                     } 
34                 } else { // 当是'?'时,只需要判断s还剩下足够的长度与匹配pcount[pi]的长度
35                     for (int i = 0; i + pcount[pi] <= slen; ++i) {
36                         (*cur)[i + pcount[pi]] = (*pre)[i] && s[i + pcount[pi] - 1] != 0;
37                     }
38                 }
39             } else {
40                 // 是'*'时就思路与正则的一致的
41                 (*cur)[0] = (*pre)[0];
42                 for (int i = 0; i < slen; ++i) {
43                     (*cur)[i + 1] = (*pre)[i + 1] || (*pre)[i] || (*cur)[i];
44                 }
45             }
46             // 这里注意下一个pi是前进了pcount[pi]
47             pi += pcount[pi];
48             // 别忘了交换
49             swap(cur, pre);
50         }
51         return (*pre)[slen];
52     }
53 };

这段代码是290ms过的,感觉还有点慢,先说说这里的缺点吧

时间最坏的情况还是不可避免的O(mn),空间复杂度为O(n),m是p的长度,n是s的长度,每层都要遍历一次s。

1. 对于'*'其实在检查到上一层的第一个true后,以后的就一定是true,不需要再计算了。

2. 对于'?'或者是其它非'*'其实不需要遍历字符串,只需要遍历上一层为true的就可以了。

按以上缺点,引进了队列把上一层的true都记录下来,便于当前层的计算,提交了一记,268ms过了。没甚提升。

实在不太甘心,但又已经没思路了。于是。。

搜了下有没别的解法,然后就发现了小磊哥的http://fisherlei.blogspot.com/2013/01/leetcode-wildcard-matching.html(可能需要FQ)

代码写得很简短,我就format一下,然后贴过来吧,个人跑了下是120ms过的。

 1 class Solution {
 2 public:
 3     bool isMatch(const char *s, const char *p) {
 4         bool star = false;
 5         const char *str, *ptr;  
 6         for(str = s, ptr =p; *str!='\0'; str++, ptr++)  { 
 7             switch(*ptr) { 
 8                 case '?':  break;  
 9                 case '*': 
10                     star =true; 
11                     s=str, p =ptr; 
12                     while(*p=='*') 
13                         p++;  
14                     if(*p == '\0') // 如果'*'之后,pat是空的,直接返回true    
15                         return true;    
16                     str = s-1;    
17                     ptr = p-1;    
18                     break;    
19                 default: {    
20                     if(*str != *ptr) {    
21                         // 如果前面没有'*',则匹配不成功    
22                         if(!star)    
23                             return false;    
24                         s++;    
25                         str = s-1;    
26                         ptr = p-1;    
27                     }    
28                 }    
29             }    
30         }    
31         while(*ptr== '*')    
32             ptr++;    
33         return (*ptr == '\0');    
34     }
35 };

初看真没看太懂,debug一遍才知道了个究竟,其实思路是贪心。

这里先要看到这个通配符匹配与正则那道的最大区别,这里只有'*'是变长匹配的,其它都是单字符的匹配。于是在匹配的模板上其实只有两种

1. '*':任意长度的匹配

2. '?'或任意非'*'字符:单字符匹配。

再联系本人动态规划的最终思路,用到了整块匹配的思路,但仅仅局限于连续出现的子串,是不是还可以拓展一下呢?

针对刚说过的通配符的分类方法,其实就可以把连续出现的单字符匹配模板作为一个整体匹配。

明白这个之后说说我对以上代码的理解吧。

这段代码中心思想是贪心,而匹配的单元分为两种

1. 连续的'*'子串,意义上其实是单个'*'

2. 连续的单字符匹配的子串

那怎么个贪心呢?

先看个例子吧:

s="aaabcaaabcaaabc", p="*bc*bc"

还是以动态规划的思路去分析吧,只是因为这符合贪心的思路,可以不需要整个DP都进行了。

p的匹配单元分别为{"*", "bc", "*", "bc\0"}(不要忘了最后的一个'\0')

p[1]: "*",按动态规划的数组,整行都会是true

p[2]:"bc",按动态规划走过来,就会有三个地方是符合的

  aaabcaaabcaaabc

好了,这里就开始考虑,能不能贪心了。

而在这里贪心的意思就是,我们匹配到了第一个"bc"时,是不是还需要再匹配下一个"bc"。

因为我们把p分成了'*'连续与非'*'连续的两种子串,那么"bc"的下一个子串若存在的话,一定就是"*"。

有这样的前提,我们来看看如果:

1. 第一个"bc"不是最优解时,最优解是第二或者第三个,"*"能匹配上

2. 第一个"bc"是最优解时,那没问题,我们可以继续匹配下一个了

Good Job. 这样就可以直接用贪心了!

于是到这里就只匹配第一个,往后p也只匹配后面的部分。

     aaabcaaabcaaabc

p[3]:"*",都能匹配上,先放着

p[4]:"bc\0",只有一个匹配

     aaabcaaabcaaabc

此时,p到结尾,s到结尾了,成功!

在这过程中,其实只要一段出现不匹配的情况,就可以直接返回false了。

perfect!

小磊哥写得很漂亮,不需要每次都遍历s,子串匹配时,只需要遍历部分,而空间复杂度是O(1)。

 

posted @ 2014-05-15 13:18  flowerkzj  阅读(206)  评论(0编辑  收藏  举报