这一题和第10题的匹配其实思路大致一致,不过需要做一点点优化,不然会超时。
大致的思路还是从后往前匹配,这里的’?‘可以匹配任何的字母,相当于前一题的’.’,而 '*'则是匹配空串,或者任意串。任意串的话,则是以当前位置结束的任意串。
故当出现星号时,能否匹配取决于这个星号匹配掉待匹配串当前位置往前k个字符组成的串后(可以理解成抵消了这个位置及前面的k个字符),剩余的待匹配串是否能够与星号前的串匹配。有了这个思路后,第一反应就是得到待匹配串的位置,然后循环去计算星号抵消i个值是否能够得到匹配,若中途出现了匹配,自然就可以,若到最后都没法匹配,则说明这个星号无法做到匹配,GameOver!
那么如果这么想,就掉到它的陷阱中去了,你多做了很多次运算!
我们假设你在计算待匹配串l1和模式串l2的时候出现了*号,我们来假设一种极端情况:它连续好多个都是 星号。首先第一轮大家都不抵消,算到某个值的时候我们发现不匹配了,我们开始回溯,然后我们最后一个 星号 开始抵消待匹配串中的字符,使得在某个位置前被抵消了一定次数不再匹配,因为此方案最终无法匹配,故期间我们算的所有位置是无法匹配的;然后我们回溯我们这个星号继续多抵消1个,从而再次去计算看是否能匹配,结果还是不能匹配,所以中间的都做不到匹配;我们这个星号回溯完后,会返回到上一个星号去,我们上一个星号也开始发挥能力,抵消1个,然后我们第一个星号又苦逼的从0个开始遍历。这个时候我们实际上就做了很多重复的操作了。比如之前星号抵消4个和第二个星号抵消0个,与之前星号抵消3个第二个星号抵消1个…期间由于 星号 的不同组合,某个位置(p,q)是否匹配被算了很多很多次。
是不是看不懂???看不懂那就看能不能理解这句话:某个数可以被多组两个数组成(100 = 1+ 99 = 2+98 = 3 + 97 …)
当最右边的两个连在一起的星号分别取0 1 和 1 0时(这里表示抵消的字符数目),它们左边的所有字符都被算了两次!
那我们怎么避免这个重复计算呢?我们多用点空间来储存(l1,l2)的匹配结果就好啦,比如l1 = 7,l2 = 9 表示待匹配串第7个位置及左边的串是否能够与模式串第9个位置及左边的串匹配即可。当之后再次来判断的时候,我们无需重新计算一次,只需要根据已经算好的值返回即可。
好啦,Show Code
#include<cstdlib>
#include<cstring>
class Solution {
public:
bool CanMatch(string &s,string &p,int l1,int l2,int **flag)
{
if(l1 < 0 && l2 < 0)//同时结束了
return true;
if(l1 >= 0 && l2 < 0)//如果第一个字符串未结束,第二个已经结束了,返回失败
return false;
if(l1 < 0 && l2 >= 0)//第一个结束了,第二个还未结束 若l2全为*则为true,否则为false
{
while(l2 >= 0)
if(p[l2--] != '*')
return false;
return true;
}
if(flag[l1][l2] != -1)
{
//已经计算了
return flag[l1][l2] == 1;//若为1,返回true,否则返回false
}
//若p这个不是?,也不是*,
if(p[l2] != '?' && p[l2] != '*')
{
if(s[l1] != p[l2])
{
flag[l1][l2] = 0;
return false;
}
if(CanMatch(s,p,l1-1,l2-1,flag))
{
flag[l1][l2] = 1;
return true;
}
flag[l1][l2] = 0;
return false;
}
else
{
if(p[l2] == '?')
{
if(CanMatch(s,p,l1-1,l2-1,flag))
{
flag[l1][l2] = 1;
return true;
}
flag[l1][l2] = 0;
return false;
}
else//如果这个是*号,则可能匹配剩余长度为l1的字符串
{
//若匹配长度为0的,则CanMatch(s,p,l1-0,l2-1);
//若匹配长度为1的,则CanMatch(s,p,l1-1,l2-1)
//若匹配长度为2的,则CanMatch(s,p,l1-2,l2-1)
for(int i = 0;i <= l1+1;++i)
if(CanMatch(s,p,l1-i,l2-1,flag))
return true;
flag[l1][l2] = 0;
return false;
}
}
flag[l1][l2] = 0;
return false;
}
bool isMatch(string s, string p) {
//从后往前匹配,当无法匹配时,则说明失败了
//若是? 则跳过这个字符继续匹配
//若p全为*,则成功
if(s.size() == 0)//若s为空字符串且p全为*,则true
{
for(int i = 0;i < p.size();++i)
if(p[i] != '*')
return false;
return true;
}
else if(p.size() == 0)
return false;
int **flag;
flag = (int **)malloc(sizeof(int*)*s.size());
for(int i = 0;i < s.size();++i)
{
flag[i] = (int *)malloc(sizeof(int)*p.size());
for(int j = 0;j < p.size();++j)
flag[i][j] = -1;
}
return CanMatch(s,p,s.size()-1,p.size()-1,flag);
}
};
这是之前超时的数据
“aaaababbbaaabaabbbbabaababaabbabbaabababbaaaaaaabba”
“baaaababab****”
上面原理若看不懂不妨把数据简化手推一遍:
“aabba”
“ba**”
Good lucky!