总共用了两种解法,一种是我自己递归求解的,效率很低,边界条件很难理清楚,代码很长,第二种是DP解法,把递归的子问题的解存到一个数组里,代码很短。
1. 递归解法
主要想法是根据当前的正则表达式,来决定接下去如何匹配,我们可以用两个字符串的子串去匹配,麻烦的是,可能相同的子字符串被求解了很多遍,特别是.*对应的字符串需要循环求解。写完这个解法以后,我很自然地就知道可以用DP递归求解了,不过边际条件比较难确定,我们可以
struct match
{
bool isMatch;
char c;
int pos;
};
class Solution {
public:
vector<match> matchArray;
string reg;
bool Match(string s, int current){
for (int i = 0; i < s.length(); ++i)
{
if (current >= matchArray.size())
{
return false;
}
match m = matchArray[current];
if (m.c == '.' && m.isMatch == true)
{
if (current == matchArray.size() - 1)
{
return true;
}
else
{
while(matchArray[current].isMatch){
current++;
if (current >= matchArray.size())
{
return true;
}
}
char c = matchArray[current].c;
bool isMatch = matchArray[current].isMatch;
for (int j = i; j < s.length(); ++j)
{
if (Match(s.substr(j), current))
{
return true;
}
}
return false;
}
}
else if (m.c == '.')
{
current++;
}
else if (m.isMatch == true)
{
if (Match(s.substr(i), current + 1))
{
return true;
}
else if (s[i] != m.c)
{
return false;
}
while(s[i] == m.c){
i++;
if (Match(s.substr(i), current + 1))
{
return true;
}
}
return false;
}
else if (m.c != s[i])
{
return false;
}
else{
current++;
}
}
for (int i = current; i < matchArray.size(); ++i)
{
if (matchArray[i].isMatch == false)
{
return false;
}
}
return true;
}
bool isMatch(string s, string p) {
reg = p;
if (p.length() == 0 && s.length() == 0)
{
return true;
}
else if (p.length() == 0 /*|| s.length() == 0*/)
{
return false;
}
for (int i = 0; i < p.length(); ++i)
{
match m;
m.pos = i;
m.c = p[i];
if (i + 1 < p.length() && p[i+1] == '*')
{
m.isMatch = true;
i++;
}
else{
m.isMatch = false;
}
matchArray.push_back(m);
}
return Match(s, 0);
}
};
- DP解法
我们可以顺其自然地记录子问题的解,代码里注释比较全。
// 既然可以递归求解,那应该可以记录子节点的情况,做DP
// 我们选择的是p的长度为j的子串是否匹配s的长度为i的子串
class Solution {
public:
bool dp[100][100];
bool isMatch(string s, string p) {
dp[0][0]=true;
for (int i = 1; i <= s.length(); ++i)
{
dp[i][0]=false;
}
int len1 = s.length();
int len2 = p.length();
for (int i = 0; i <= len1; ++i)
{
// 注意i=0对应的是s是空串的情况
for (int j = 1; j <= len2; ++j)
{
// 同样的j=0对应的是p=0的情况,而且我们已经初始化过了
// i,j对应的是字符串的
if (p[j-1] == '*')
{
if (p[j-2]=='.' || (i>0&&p[j-2]==s[i-1]))
{
// p_j-2可以匹配s的结尾,那么只需要匹配情况和长度为i-1的s一样就可以了
// 主要是x*可以匹配0/1个:
// 1 如果p可以匹配s,则px*一定匹配sx
// 2 如果p不可以匹配s,则:
// 2.1 如果希望px*匹配sx,那么说明x*匹配的不是单一的x:
// 2.1.1 x*匹配的是空,则p匹配sx也就是dp[i][j-2]=true
// 2.1.2 x*匹配的是xxxxx,做了k次匹配,则px*一定也可以匹配sxxxx(k-1个x)
// ,也就是dp[i-1][j]=true(还要求i>0)
// 2.2 匹配的情况只有以上两种,不匹配的话取反就可以了,而dp[i][j-2]已经做了相应的记录,直接用就好
// 而且因为*不会出现在p开头,所以j>=2
// 这里可能会出现一些误解,只要想清楚p[j-2]对应的长度是j-1,dp[i][j-2]对应的长度是j-2就可以
dp[i][j]=(i>0&&dp[i-1][j])||dp[i][j-2];
}
else{
// 如果不能匹配结尾,说明x*毫无作用,它做了0次匹配,对应的是2.1.1的情况
dp[i][j]=dp[i][j-2];
}
}
else if (i>0 && (p[j-1]=='.' || p[j-1]==s[i-1]))
{
// 这里如果p可以匹配s,那么p.可以匹配sx,如果p不可以匹配s,那么p.也不可以匹配sx
// px和sx是同理的
dp[i][j]=dp[i-1][j-1];
}
else{
// 其他情况下,也就是说,pa不匹配sb,那么我们暂时让它等于false
// 这个条件,我在yy的时候,一直在想p有.*的情况,好像理不清楚的样子
// 其实很简单,p'.*a和sb一样是不匹配的,p'.*其实在第一步讨论过了
dp[i][j]=false;
}
}
}
return dp[len1][len2];
}
};