剑指 Offer 19. 正则表达式匹配 - 力扣(LeetCode)一样的题目
困难题,第一个难点就是读懂题目。。。
示例3我卡了好久啊,明明一眼就该看出来的东西,哭。。。
输入:
s = “ab”
p = “.*”
输出: true
解释: .*
表示可匹配零个或多个*
任意字符.
,这句话怎么读都读不通顺,理解为*
匹配成前面的.
,于是就变成..
,就想开了。
递归
思路是不停地对头部元素进行匹配,如果头部元素相同,去掉头部再匹配剩余的字符串…
但是,如果头部元素不同,就返回false
吗?并不是,看这个例子:
s: ab
p: b*ab
首元素s[0]、p[0] 不同,但实际上能匹配,因为 *
(p[1]) 的特殊功能,可以将 b
变为0个(也就是为空)。*
的特殊功能仅仅对它的前面那个字符生效,所以需要往p的首元素后面多考虑一个元素,也就是p[1]。
1、如果p
的第二个元素不为 *
,那么仅仅考虑首元素即可:
s[0] == p[0]
,递归判断isMatch(s.substr(1), p.substr(1));
s[0] != p[0]
,返回false
2、如果p
的第二个元素p[1]
为 *
时,需要考虑几种情况:
s[0] != p[0]
,此时要想匹配能够进行下去,必须使用*
消去p[0]
,也就是说接着判断isMatch(s, p.substr(2));
即可,示例4的第一次消除情况就是这个。s[0] == p[0]
:
a、可以选择使用*
消去p[0]
,接着判断isMatch(s, p.substr(2));
b、不进行消去的话,接着判断isMatch(s.substr(1), p);
以上两种选择任一种能够匹配成功即可。
比较难理解的是2.b这一条,为什么是判断isMatch(s.substr(1), p);
呢?来看看示例4:
s = "aab" p = "c*a*b"
第一步:首元素s[0] != p[0]
,但是p[1] == *
,要想匹配进行下去,必须使用 *
消除p[0]
,也就是判断isMatch(s, p.substr(2));
那么
s = "aab" p = "a*b"
第二步:首元素s[0] == p[0]
,有两个分支:
a、使用*
消去p[0]
:剩下aab
和b
,最后匹配失败
b、由于*
可以匹配零个或多个前面的那一个元素,肉眼观察的话此处我们使用*
匹配1个前面的字符,就可以匹配成功,但是怎么将这种匹配多个前面的字符的逻辑用代码表示呢?遍历到当前点我们并不知道需要匹配多少个前面的那一个元素,所以换一种方式来表达:我们通过去掉s的首元素来实现这种选择。也就是说接下来:
s = "ab" p = "a*b"
再往下还可以去掉s的首元素:
s = "b" p = "a*b"
这样就回到了第一步,就可以走下去了。
当然前面判断首元素匹配的时候记得考虑 .
。
class Solution {
public:
bool isMatch(string s, string p) {
if(p.empty()) return s.empty();
bool headMatch = !s.empty() && (p[0] == s[0] || p[0] == '.');
if(headMatch){//首元素匹配
if(p.size() > 1 && p[1] == '*'){//可以消除
if(isMatch(s, p.substr(2))) return true;//消除p[0]
if(isMatch(s.substr(1), p)) return true;
return false;
}else//无法消除
return isMatch(s.substr(1), p.substr(1));//去掉首元素
}else{//首元素不匹配
if(p.size() > 1 && p[1] == '*') return isMatch(s, p.substr(2));//进行消除
return false;//无法匹配
}
}
};
看了别人的代码,可以修改一下,不过不容易理解:
class Solution {
public:
bool isMatch(string s, string p) {
//不停地减去s,p相同的头部,直到某一个或者全部被剪空,就有结论了
if(p.empty()) return s.empty();
// 先检查首元素
bool head_match = !s.empty() && (s[0] == p[0] || p[0] == '.');
// 如果下一个元素是‘*’
if(p.size() >= 2 && p[1] == '*')
return isMatch(s, p.substr(2)) || (head_match && isMatch(s.substr(1), p));
else
// 一般情况,首元素匹配上的条件下均去掉首元素再匹配
return head_match && isMatch(s.substr(1), p.substr(1));
}
};
动态规划
状态推导:正则表达式匹配 - 正则表达式匹配 - 力扣(LeetCode)
class Solution {
public:
bool dp[35][35];//dp[i][j]表示s的前i个字符与p中的前j个字符是否能够匹配
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
dp[0][0] = true;//两个空串可匹配
for(int i = 2; i <= n; ++i){//初始化,s为空的情况
if(p[i-1] == '*') dp[0][i] = dp[0][i-2];
}
for(int i = 1; i <= m; ++i){
for(int j = 1; j <= n; ++j){
if(p[j-1] == '*'){
dp[i][j] |= dp[i][j-2];//消去
if(s[i-1] == p[j-2] || p[j-2] == '.') dp[i][j] |= dp[i-1][j];
}
else if(s[i-1] == p[j-1] || p[j-1] == '.') dp[i][j] |= dp[i-1][j-1];
}
}
return dp[m][n];
}
};