题目
Implement regular expression matching with support for '.'
and '*'
.
'.' Matches any single character.
'*' Matches zero or more of the preceding element.
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", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true
思路
此题第一次做没想出来怎么解,最后参考了这篇文章。详细的思路请见该博客。
看完该博客的思路和代码之后,发现很多情况都可以用到递归的,递归也可以使代码更加优雅。
代码
bool helper(const char *s, const char *p, int i, int j) {
int sLen = strlen(s);
int pLen = strlen(p);
if(j == pLen)
return i == sLen;
if((j == pLen - 1) || (p[j + 1] != '*')) {
if((i == sLen) || ((s[i] != p[j]) && (p[j] != '.')))
return false;
else {
return helper(s, p, i + 1, j + 1);
}
}
// p[j + 1] == '*'
while((i < sLen) && ((p[j] == '.') || (s[i] == p[j]))) {
if(helper(s, p, i, j + 2))
return true;
i++;
}
return helper(s, p, i, j + 2);
}
bool isMatch(const char *s, const char *p) {
return helper(s, p, 0, 0);
}
下图是上述代码运行的一个例子:
另一种思路:动态规划
如这篇文章所述,上述所用的递归方法会存在重复计算的问题,导致效率不高。下面用动态规划的思想解决此题(动态规划的思想可以参考《算法导论》第15章或者这篇文章)。
还是以上面的两个字符串(s = "abcdddg"
,p = "a.*d*g"
)为例。其中,字符串 s
的长度记为 sLen
,字符串 p
的长度记为 pLen
。
先构造一个 (pLen + 1)
行 (sLen + 1)
列的二维数组(表) table[][]
用于记录当前的结果。这个二维数组的元素取值为 true
或者 false
。
其中,table[j][i]
为 true
表示字符串 p
的前 j
个字符组成的字符串(p[0 ... j - 1]
)能够匹配 s
的前 i
个字符组成的字符串(s[0 ... i - 1]
)。依此类推,当 table[pLen][sLen]
为 true
时,则 s
和 p
相匹配,即 isMatch()
函数返回 true
。
table[][]
初始状态和匹配完成后的状态:
Note: 索引
i
和j
在字符串与二维数组之间的关系。p[j]
和s[i]
两个字符对应在table[][]
中的位置是table[j + 1][i + 1]
(p[j], s[i] => table[j + 1][i + 1]
)。
在对比时主要分如下几种情况。
p[j] != '*'
:如果字符 p[j] == s[i]
或者 p[j] == '.'
(即 p[j]
、s[i]
这两个字符匹配),那么这两个字符在 table[j + 1][i + 1]
这个点的结果与字符 p[j - 1]
、s[i - 1]
在 table[j][i]
这个点的结果一样,即 table[j + 1][i + 1] = table[j][i]
。
p[j] != '*'
这种情况的判断代码为:
for(i = 0; i < sLen; i++) {
if(s[i] == p[j] || p[j] == '.') {
table[j + 1][i + 1] = table[j][i];
}
}
p[j] == '*' && p[j - 1] == '.'
:即字符串 p
中出现 ".*"
这个子字符串。当然,这种情况需要 j > 0
。
".*"
这个可以匹配任何字符串。如题目所述,'.'
匹配任意单个字符('.'
可以替换成 'a'
、'b'
……);'*'
可以替换成0个、1个或者多个前一个字符('a*'
可以替换成 ''
、'a'
、'aa'
、'aaa'
……)。
因此,当子字符串 s[0 ... i]
与 ".*"
前面的子字符串(即 p[0 ... j - 2]
)匹配(这个条件相当于 table[j - 1][i + 1]
为 true
),那么 s
的子字符串 s[0 ... i + 1]
、s[0 ... i + 2]
、…、s[0 ... sLen - 1]
都与 p
的子字符串 p[0 ... j]
相匹配(这个结果就相当于 table[j + 1][i + 1]
、table[j + 1][i + 2]
、…、table[j + 1][sLen]
均为 true
)。
还有一些特殊的情况需要用到 table[j][i + 1]
去判断:
s = "aa", p = ".*"
,这样的话上述的子字符串 p[0 ... j - 2]
就不存在了(此时 j = 1
,j - 2
不存在)。因此此时需要判断 table[j][i + 1]
是 true
还是 false
了。
s = "ba", p = "a*.*"
,这种情况也需要判断 table[j][i + 1]
。
p[j] == '*' && p[j - 1] == '.'
这种情况的判断代码为:
i = 0;
while(i < sLen && !table[j - 1][i + 1]
&& !table[j][i + 1]) {
i++;
}
for(; i < sLen; i++) {
table[j + 1][i + 1] = true;
}
p[j] == '*' && p[j - 1] != '.'
:这种情况下,table[j - 1][i + 1]
或者 table[j][i + 1]
这两个条件上面已经讲过。i > 0 && j > 0 && table[j + 1][i] && s[i] == s[i - 1] && s[i - 1] == p[j - 1]
这个条件表示子字符串 s[0 ... i - 1]
与子字符串 p[0 ... j]
匹配并且字符 s[i]
与 s[i - 1]
和 p[j - 1]
匹配。
p[j] == '*' && p[j - 1] != '.'
这种情况的判断代码如下:
for(i = 0; i < sLen; i++) {
if(table[j - 1][i + 1] || table[j][i + 1]
|| i > 0 && j > 0 && table[j + 1][i]
&& s[i] == s[i - 1] && s[i - 1] == p[j - 1]) {
table[j + 1][i + 1] = true;
}
}
代码如下。这版代码提交到 leetcode.com 后,可以看到所用的时间的确比递归的方法短。
代码
bool isMatch(const char *s, const char *p) {
int i = 0, j = 0;
int sLen = strlen(s);
int pLen = strlen(p);
bool res = false;
bool **table = NULL;
if(sLen == 0 && pLen == 0)
return true;
if(pLen == 0)
return false;
// Initialize table with false.
table = (bool **)malloc((pLen + 1) * sizeof(bool*));
for(j = 0; j <= pLen; j++) {
*(table + j) = (bool *)malloc((sLen + 1) * sizeof(bool));
}
for(j = 0; j <= pLen; j++) {
for(i = 0; i <= sLen; i++) {
table[j][i] = false;
}
}
table[0][0] = true;
i = 0;
j = 0;
for(j = 0; j < pLen; j++) {
if(p[j] != '*') {
for(i = 0; i < sLen; i++) {
if(s[i] == p[j] || p[j] == '.') {
table[j + 1][i + 1] = table[j][i];
}
}
}
else {
if(j > 0) {
if(table[j - 1][0])
table[j + 1][0] = true;
if(p[j - 1] == '.') {
i = 0;
while(i < sLen && !table[j - 1][i + 1]
&& !table[j][i + 1]) {
i++;
}
for(; i < sLen; i++) {
table[j + 1][i + 1] = true;
}
}
else {
for(i = 0; i < sLen; i++) {
if(table[j - 1][i + 1] || table[j][i + 1]
|| i > 0 && j > 0 && table[j + 1][i]
&& s[i] == s[i - 1] && s[i - 1] == p[j - 1]) {
table[j + 1][i + 1] = true;
}
}
}
}
}
}
res = table[pLen][sLen];
for(j = 0; j <= pLen; j++) {
free(*(table + j));
*(table + j) = NULL;
}
free(table);
table = NULL;
return res;
}