说实话,这个题目确实是到目前为止练习的最难的一道题目,但是也是意义非常重大的一道题目,它点明了动态规划的原理。
首先,我们仔细审题,先进行第一步思考,a*表实可以有0个a,也可以有任意个a,这样就可以把p转换成多个字符串,只要s和其中一个相等,就可以匹配。
再深入想想,其实正则表达式匹配是一个“策略”的问题。比如s="aaa",p="a*a",时,就有各种匹配方式。其中有的可以匹配,有的不能匹配。只要有一种方式匹配,就说明可以。每次遇到a*时,我们都要做出选择,选择匹配0,1,2等等。想一下我们对比字符串的方式,就可以想出避免无限循环的方法——匹配时不是从p中增加若干个a来匹配s,而是从s中去掉若干个a,如果没法去a则循环终止。
编写程序的时候,由于题目有“选择”成分,想到图的搜索(其实就是暴力走迷宫),我们就可以采用递归的方式。(用的官方题解,自己的程序有点问题)
class Solution {
public boolean isMatch(String text, String pattern) {
if (pattern.isEmpty()) return text.isEmpty();
boolean first_match = (!text.isEmpty() &&
(pattern.charAt(0) == text.charAt(0) || pattern.charAt(0) == '.'));
if (pattern.length() >= 2 && pattern.charAt(1) == '*'){
return (isMatch(text, pattern.substring(2)) ||
(first_match && isMatch(text.substring(1), pattern)));
} else {
return first_match && isMatch(text.substring(1), pattern.substring(1));
}
}
}
但是这毕竟又不是走迷宫,分析过程我们发现,对于字符串mississippi和mis*is*ip*.,如果issippi匹配成功的话,判断sissippi就不用整体判断一遍,也就是说,子问题的答案可以复用。
于是根据算法导论中作图(做出来就是一个二维表),使用二维数组来存储中间结果,就可以使用更低的时间复杂度来完成这个题目。由于每一步都在填这个二维表,时间复杂度很显然就是O(TP)。(至于状态,状态转移方程之类严谨的东西,之后再补充吧)
bool isMatch(const string& s,const string& p)
{
mem = vector<vector<int>>(s.size() + 1, vector<int>(p.size() + 1, -1));
return doMatch(s, 0, p, 0);
}
bool doMatch(const string& s,int sIndex, const string& p, int pIndex)
{
if (mem[sIndex][pIndex] != -1)
{
return mem[sIndex][pIndex];
}
bool res;
if(pIndex >= p.size()) res = (sIndex >= s.size());
else
{
bool currentMatch = (sIndex < s.size() && (s[sIndex] == p[pIndex] || p[pIndex] == '.'));
if(pIndex+1 < p.size() && p[pIndex+1] =='*')
{
res = doMatch(s, sIndex, p, pIndex+2) ||
currentMatch&&doMatch(s, sIndex + 1, p, pIndex);
}
else
{
res = currentMatch&&doMatch(s, sIndex+1, p, pIndex + 1);
}
}
mem[sIndex][pIndex] = res;
return res;
}