参考资料
题目: 请实现一个函数用来匹配包括’.‘和’*‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配.
提示:如果输入空字符串,则返回True
解题方法:递归回溯
设匹配串为:s 范围:a-z
模式串为:p 范围:a-z 或者 ‘.’ ,’*’
解题思路:要解决这道题,首先我们应该明确以下几点隐含信息
- 1 ‘*’肯定不会出现第一个字符的位置,因为‘*’需要以前一个字符做参照。
- 2 由于‘*’需要依靠前一个字符来判断,所以对于有‘*’和无‘*’的判断情况不同。
知道了上面的隐含信息后,我们再详细分析:
首先,我们应该知道,如果s和p的第一个字母都相同,那么说明当前字母是匹配的,即会同时向下移动。然而,我们知道’*‘必须要依靠前一个 字符来作为判断。所以,我们在判断的时候必须要考虑p的第二个字符,判断这个字符是否为‘*’,因为这个字符是否为’*'处理方式不同。
现在,我们知道每次需要判断p的第二个字符是否为’*’,因此,我们很自然的想到将判断情况分为,p的第二个字符为‘*’和第二个字符不为‘*’两种情况。对于不为‘*’这种情况很好处理,看下面分析。
如果p的第二个字符不为‘*’
对于此种情况,我们需要判断s 和 p的第一个字符是否相等,如果相等(相等包括字符相等和p的字符为‘.’这种情况)就分别截取这两个字符去除第一个相同字符的子串,继续判断(递归),否则就返回false;
如果p的第二个字符为‘*’
对于这种情况,就比较复杂了。
首先看个例子:
s : “pq”
p : “d*pq”
情况一:’*'的匹配字符为0个时
s 和 p 分别为如上字符串,此时p的第二个字符为‘*’,这时,我们保持s不变,p直接略过这两个字符,截取剩下的子字符串,如下图所示:
此时,即‘*’的匹配为0个,显然上述字符匹配成功,结束。
情况二:’*'的匹配字符为1个或多个时
s : “ffghj”
p : “f*ghj”
s 和 p 分别如上字符串,此时p 的第二个字符为‘*’,这时,我们保持p不变,如果字符匹配,那么我们就截取s 即去掉s的首元素,去掉s的首元素后变为"fghj",如下图所示:
如上图所示,在进行匹配之后,发现第一个字符还是匹配的,因此,我们继续上面的匹配,如下图:
如上图所示,两次匹配后编程上图所示,我们发现这种情况不就是情况一对应的吗?即此时利用情况一即可完成匹配,如下图所示:
下面我们再来看另一种情况:
s : “ffghj”
p : “f*fbc”
这种情况与上面唯一的区别就是在*后面又出现了前面匹配过得字符,我们看一下匹配结果:
首先按照情况二匹配:第一个字符匹配成功,如下图
继续按照情况二匹配:
此时,我们发现这最终的结果是失败的。
然而当我们按照情况一进行匹配时,如下图:
我们发现也不匹配,然而实际山这个s 和 p是匹配的,我们看一下下面的匹配方式:
即刚开始我们首先使用情况二进行匹配,匹配后变成如上情况,接着我们再按照情况一进行匹配,如下:
我们发现这样就会匹配成功。
总结:对于p的第二个字符为\*的这种情况,如果只是单一的按照情况一或者情况二进行匹配都会失败,当他们俩结合时可能会匹配成功,是否成功这取决于情况一和情况二遍历的顺序。如果s 和 p 本身就是匹配的,那么只要对所有情况进行遍历,那么总会找到一种成功的匹配路径,而递归就有包括潜在意义上的遍历,因此,只要s 和 p 本身是匹配的,那么我们通过递归一定能找到一种方法来完成匹配。
代码如下
class Solution {
public:
bool match(const string& str,const string& pattern){
if(pattern.empty())
return str.empty();
//有*的情况
bool first_match = !str.empty() && (str[0] == pattern[0] || pattern[0] == '.');
if(pattern.size() >= 2 && pattern[1] == '*'){
return match(str, pattern.substr(2)) ||
(first_match && match(str.substr(1),pattern));
}else{//无*的情况
return first_match && match(str.substr(1), pattern.substr(1));
}
}
};