RebornLee 2019-08-21
https://leetcode-cn.com/probl...
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
输入示例:
输入:
s ="aab"
p ="c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。输入:
s ="mississippi"
p ="mis*is*p*."
输出: false
自己最开始想到的方法,相当于是没有回溯的回溯法,导致s='aaa' p='a*a'这种情况匹配失败
大体上就是通过递归的手段把匹配的问题抛给下一层
先从简单的想起,如果没有'.'和'*',那么字符串的匹配就是很简单的两个指针i和j,分别对着s和p,然后依次对比。
这时,考虑加上'.',对比字符s[i]和p[j]的时候,只需要把s[i]==p[j]加上p[j]=='.'的判断就可以
最后,再加上'*'的逻辑,根据对示例的观察,可以发现遇到*有这样几种情况(根据示例归纳出规律):
aab和c*a*b,这里c*并没有匹配成功a,所以c*的含义就是取0个c
aab和a*b,注意这里有两种情况
完整的aab匹配c*a*b的过程如下图:
写出伪代码
if p[j+1]=='*' and s[i]==p[j]:
j+2 recursive || i+1 recursive
if p[j+1]=='*' and s[i]!=p[j]:
j+2 recursive
if p[j+1] != '*':
if s[i]==p[j]:
i+1 and j+1 recursive
else:
return false递归出口,
基于暴力解法,通过自顶向下的备忘录方式,或者自底向上的方式记录信息
这种是标准的正则表达式的解决方式,通过解析请求的pattern,然后画出来一幅该正则式的有限状态机图,然后再对着图匹配字符串
这个是个错误解法,aaa和a*a的情况会由于指针没有回溯导致匹配失败,稍后分析
class Solution {
public boolean isMatch(String s, String p) {
int i = 0, j = 0;
while(i < s.length() && j < p.length()) {
if(s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') {
if(j + 1 < p.length() && p.charAt(j+1) == '*') {
i++;
} else {
i++;
j++;
}
} else if(j + 1 < p.length() && p.charAt(j+1) == '*') {
j += 2;
} else {
return false;
}
}
while(i == s.length() && j + 1 < p.length() && p.charAt(j+1) == '*') {
j+=2;
}
if(i == s.length() && j == p.length()) {
return true;
} else {
return false;
}
}
}class Solution {
public boolean isMatch(String s, String p) {
return match(s, p, 0, s.length(), 0, p.length());
}
private boolean match(String s, String p, int sbegin, int send, int pbegin, int pend) {
if(sbegin >= send && pbegin >= pend) {
// exit
return true;
}
if(pbegin + 1 < pend && p.charAt(pbegin + 1) == '*') {
if(sbegin >= send) {
// s is empty
return match(s, p, sbegin, send, pbegin+2, pend);
}
if(s.charAt(sbegin) == p.charAt(pbegin) || p.charAt(pbegin) == '.') {
return match(s, p, sbegin, send, pbegin+2, pend) || match(s, p, sbegin+1, send, pbegin, pend);
} else {
return match(s, p, sbegin, send, pbegin+2, pend);
}
} else {
if(sbegin >= send || pbegin >= pend) {
// exit
return false;
}
if(s.charAt(sbegin) == p.charAt(pbegin) || p.charAt(pbegin) == '.') {
return match(s, p, sbegin+1, send, pbegin+1, pend);
} else {
return false;
}
}
}
}graph TD A(分析例子) --> B(归纳出规律) B --> C(画图) C --> D(写伪代码确定递归推进和出口)
容易忘掉,例如aaa匹配a*a,匹配成功了a*也是可以当做匹配了0个的,此外注意指针直接推进是不行的,匹配完之后必须要判断后面的子串是否匹配成功(以此为依据才想到了递归)
不然容易出现a*直接把aaa都匹配完了,算作匹配失败
整体流程字符流 -> 状态机 -> 词token -> 栈 -> dom构建 DOM 的过程是:从父到子,从先到后,一个一个节点构造,并且挂载到DOM树上。<p class="a">text text