分析:
本题可用递归或者动态规划解决。
下面介绍递归的解法:
与一般字符串匹配不同的是,这题多了.和星号两个匹配符;对于.可以匹配任何字符,处理较为简单,但是对于星号处理较为复杂。
为了分类更加简洁,我们在比较s[a]与p[b]时,不先去比较这二者是否相等,而是去判断p[b+1]是否为星号,不为星号的话,直接比较s[a]和p[b],匹配时指针均右移,如果p[b+1]等于星号,那么我们分以下几种情况:
1.s[a] != p[b],意味着如果星号不表示把p[b]出现次数置为0,那么就不匹配,所以此时我们考虑下一种状态match(s,p,a,b+2)。
2.s[a] == p[b],此时就是不确定性有限状态机问题了,我们可以进一步转化为三种状态。
第一种:和1一样,即使当前字符匹配,我们还是让它出现的次数为0,说不定p后面的字符还会和s当前字符匹配呢,这种情况我们容易忽略,也就是match(s,p,a,b+2)。
第二种:星号使得p[b]出现一次,也就是b直接右移两位进行比较即可。即match(s,p,a+1,b+2)。
第三种:星号使得p[b]出现不小于2次,那么可以递归的转化为子问题,b不变,a右移一位。即match(s,p,a+1,b)。
如何说上面分类比较难以想到的话,那么这题的边界情况同样需要小心翼翼,稍微出错便不能ac。
s与p都为空,匹配,但是s为空也可以匹配p不为空,比如p为2星号,星号可以让前面字符出现次数变为0。当然,一旦模式串p为空,而s非空,那么肯定是不匹配的。
从下面代码可以看见,边界情况用了两条语句,在b刚刚越界时判断一下a是否也越界了;另外,由于分类时首先考虑的是p[b+1],所以还需要判断b是否是最后一个字符,如果是,只有在a也到达最后的字符且匹配整体才匹配。
原代码
class Solution {
public:
bool isMatch(string s, string p) {
if(!p.size()) return !s.size();
return match(s,p,0,0);
}
bool match(string s,string p,int a,int b){
if(b == p.size()) return a == s.size();
if(b + 1 == p.size()) return a + 1 == s.size() && (s[a] == p[b] || p[b] == '.');
if(p[b+1] != '*'){
if(s[a] == p[b] || p[b] == '.') return match(s,p,a+1,b+1);
return false;
}
if(s[a] != p[b] && p[b] != '.') return match(s,p,a,b+2);
return match(s,p,a,b+2) || match(s,p,a+1,b+2) || match(s,p,a+1,b);
}
};
感谢灞波儿奔同学指出上面代码的瑕疵,现对思路以及代码稍微修改下,使其具有更强的鲁棒性。
从反例s = “ab”,p = “.*c”可以看出,当*促使前面的.出现0,1,2次时都无法匹配,此时代码会继续往后比对,从而使得a超过了s的边界出现运行错误。之前代码没有在访问a下标时判断边界是因为a到达s的边界时,即使b还未到达p的边界,依旧可能匹配,比如s = “ab”,p = “abc*“,*使得c出现的次数是就可以匹配。为了防止a越过s的边界,并且保证算法的正确性。加上了对a == s.size()这种情况的特判,除非此时b位置的后一位是*,才将b继续右移两位再次比对,否则本次匹配失败。
修改后的代码:
class Solution {
public:
bool isMatch(string s, string p) {
if(!p.size()) return !s.size();
return match(s,p,0,0);
}
bool match(string s,string p,int a,int b){
if(b == p.size()) return a == s.size();
else if(a == s.size()){
if(b == p.size() -1 || p[b + 1] != '*') return false;
return match(s,p,a,b + 2);
}
if(b + 1 == p.size()) return a + 1 == s.size() && (s[a] == p[b] || p[b] == '.');
if(p[b+1] != '*'){
if(s[a] == p[b] || p[b] == '.') return match(s,p,a+1,b+1);
return false;
}
if(s[a] != p[b] && p[b] != '.') return match(s,p,a,b+2);
return match(s,p,a,b+2) || match(s,p,a+1,b+2) || match(s,p,a+1,b);
}
};
大佬,你这个算法存在一个问题,下面这个例子就无法通过s=”ab”, p=”.*c”,会出现stak-buffer overflow,也就是字符串访问越界了,原因是”.*”可以匹配所有的s串,因此会存在一种情况就是a一直加,但是b不动,当a=s的长度时就报错了。虽然知道问题在哪,但是不知道怎么改,真实太菜了。给大佬提个醒,希望完善一下。
感谢提醒,已修改题解和代码。
求助:这个代码现在放在leetcode上运行会报错 提示stack-buffer overflow
可能LeetCode上这题数据量比较大,爆栈了,你就改用y总的dp思路吧。
好的。不过你的解析写的真的一级棒 我看一遍就懂了!比心
修改了下代码,你再去试试看看还会不会栈溢出。
感觉精简了一下?
class Solution {
public:
bool isMatch(string s, string p) {
if(!p.size()) return !s.size();
return dfs(s,p,0,0);
}
bool dfs(string s,string p, int a,int b)
{
if(b==p.size()) return a==s.size();//b越界,a是否越界
if(b+1==p.size()) return a+1==s.size()&&(s[a]==p[b]||p[b]==’.’);//a是最后一个字符,b是否是最后一个并且匹配
if(p[b+1]!=’*’)
{
if(s[a]==p[b]||p[b]==’.’)
return dfs(s,p,a+1,b+1);
else
return false;
}
else
{
if(s[a]==p[b]||p[b]==’.’)
return dfs(s,p,a,b+2)||dfs(s,p,a+1,b);
else
return dfs(s,p,a,b+2);
}
}
};
这么多分支调用会不会暴栈啊?
不会的,题目数据量应该不大