难度:Hard
描述:
Implement regular expression matching with support for '.'
and '*'
.
'.' Matches any single character.
'*' Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
The function prototype should be:
bool isMatch(const char *s, const char *p)
Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true
思路:
真的难,最开始的思路是循环用p字符串中的每一部分一步一步来匹配s字符串,在*这里卡住了,这种方法会贪婪匹配所有的X*的X,如果
p为:a*a s为:aaa
那么就完全匹配不了了,需要后面的字符信息来完成匹配,因为你不知道a*到底对应几个a。
想了半天,参考了其他人的答案说下我的理解:
1.递归法
递归中最难处理的是遇到X*匹配X,你说你要匹配几个呢,没有上下文的支持你是不知道的,怎么办?
诀窍在于当用X* 匹配 X时分两种情况:
1.直接不用X*匹配,跳过,相当于生成了X^0,举个例子:在p=a*a ,s=a时,你就会让a* = a^0对不对?
这就是模拟这种情况,直接在让p的计数器+2(也可以是-2,看你怎么递归),跳过这个X*,然后s计数器不变,进入下一轮的递归,这个递归返回为真,那么说明
这么跳过去是对的,X*应该匹配个空子串。
2.但是上面的递归返回为假时怎么办呢?那么就说明要完成匹配X*不能只匹配个空子串,它需要至少匹配1个X,这时将s的计数器+1,p计数器不变,进入下一轮递归
如果X*没能匹配到X,那么直接p计数器+2,下一轮递归。
对于退出,只要当p 完全被匹配完后,开始判断剩下的s长度,如果也匹配完了,说明成功。s还剩下就说明失败
然后s被匹配完后,p还剩下的话,需要在处理p的字符时判断一下是不是要返回假了。
代码:
class Solution {
public:
bool match(string& s, int i, string &p, int j){ //这个程序是从后往前匹配的,这样不用担心*导致的各种越界校验
if (j < 0){
return (i < 0);
}
if (p[j] != '*'){
if (i < 0)return false; //不是X*而已经空了,说明匹配失败
if (s[i] == p[j]|| p[j] == '.') return match(s,i-1,p,j-1);
else return false;
} else if (p[j] == '*'){
if (i < 0) return match(s,i,p,j-2); //是X*,可以让它匹配X^0,从而继续往下匹配
if (p[j-1] == s[i] || p[j-1] == '.'){
if (match(s,i,p,j-2)) return true; //如果让X*匹配X^0
else
return match(s,i-1,p,j); //不行,就让他匹配至少一个X
}else{
return match(s,i,p,j-2); //没匹配到X,计数器+2
}
}
}
bool isMatch(string s, string p) {
return match(s,s.size()-1,p,p.size());
}
};
上面的解法比较容易理解,但是效率较低。
这个问题可以使用动态规划解决。
首先要找出状态转移的方程
dp[i][k]代表p{0,1.,2.....k-1}和s{0,1,2.....i-1}的状态。bool值
初始化,dp[0][0]=true,空串匹配空串,当然为真
dp[0][i],除了i==0,为假,其他都是如果p[i-1] == '*',那么dp[0][j] = dp[0][i-2], 如果p[i-1] != '*',那dp[0][i] = false
我们可以通过dp[i-1][k-1]知道dp[i][k]的状态吗?
可以有下面几种情况:
p[k-1]不为*,那么只要知道dp[i-1][k-1]的值,然后看p[k-1]等不等于s[i-1]就行
p[k-1]为*,
这就比较麻烦,有两种情况,p[k-2]+p[k-1]不匹配任何字符,匹配个空子串 ,dp[i][k]==dp[i][k-2]
匹配了字符串,p[k-2] ==‘.' or s[i-1],且 需要 dp[i-1][k]的值,因为光有s[i-1] == p[k-2]是不够说明的,dp[i-1][k]代表这个p的k个字符匹配了之前的i-1个字符的s
上面说的p[k-1]为*时的两种情况不互斥,用||连起来
最后返回dp[i][k]
代码:
class Solution {
public:
bool isMatch(string s, string p) {
bool dp[1000][1000];
dp[0][0] = true;
for (int i = 1; i <= s.size(); i++){
dp[i][0] = false;
}
dp[0][1] = false;
for (int i = 2; i <= p.size(); i++){
if (p[i-1] == '*') dp[0][i] = dp[0][i-2];
else dp[0][i] = false;
}
for (int i = 1; i <= s.size(); i++){
for (int k = 1; k <= p.size();k++){
if (p[k-1] != '*'){
dp[i][k] = (p[k-1] == s[i-1] || p[k-1] =='.') && dp[i-1][k-1];
} else {
dp[i][k] = dp[i][k-2] || (dp[i-1][k] && (p[k-2] == '.' || s[i-1] == p[k-2])); //要么匹配空串,要么s[i-1] == p[k-2],且该字符前的
s可以和当前的p匹配,以X*举例说明,这情况下说明之前s已经有了1个以上的X
}
}
}
return dp[s.size()][p.size()];
}
};