一 题目
Given an input string (s
) and a pattern (p
), 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).
Note:
s
could be empty and contains only lowercase lettersa-z
.p
could be empty and contains only lowercase lettersa-z
, and characters like.
or*
.
Example 1:
Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".
Example 2:
Input:
s = "aa"
p = "a*"
Output: true
Explanation: '*' means zero or more of the preceding element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".
Example 3:
Input:
s = "ab"
p = ".*"
Output: true
Explanation: ".*" means "zero or more (*) of any character (.)".
Example 4:
Input:
s = "aab"
p = "c*a*b"
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore, it matches "aab".
Example 5:
Input:
s = "mississippi"
p = "mis*is*p*."
Output: false
二 分析
正则表达式的匹配。HARD难度
2.1 递归
我最开始就是想到了递归,就是针对P的不同情况去处理,但是调不出来。。。。卡在了对于*的处理上,因为*太灵活,可以有0个或者多个。从前往后匹配,有个问题就是,遇到每个字符后要考虑后面有没有“*”,还得考虑别越界。做题让你打回原形的就是,这个感觉有个思路,就是死活调不出来,真实自己太菜了。后来看了网上大神的文章。是通过对s和p串进行删减来进行比价的,每次比较时只局限在0,1下标上。这里关键点在于p[1],分别判断是不是‘*’处理。
1. p[1] != ‘*’,就是匹配第[0]位,看p[0]是不是==s[0],或者p[0]=='.'.
2.对于p[1]==‘*’,就是分两种情况分析:
条件是若s不为空且首字符匹配(包括 p[0] ==‘.’) 匹配p[2]之后的字符串(对应的case是例4要考虑到当前p的前两个字符可能匹配不到任何的s).满足直接返回true.不满足则s截除首字母(因为已经匹配过了)继续进行循环。
返回调用递归函数匹配s和去掉前两个字符的p的结果(前两位已经匹配完成)。
//递归
public static boolean isMatch(String s, String p) {
//p 长度为0
if(p.isEmpty()){
return s.isEmpty();
}else if(p.length()==1){
return s.length() == 1&&(s.equals(p)||p.equals(".") );
}else if(p.charAt(1)!='*'){
return !s.isEmpty()&&(s.charAt(0) == p.charAt(0) || p.charAt(0) == '.')&&(isMatch(s.substring(1),p.substring(1))) ;
}else{
//p[1]=='*'
//首元素匹配
while (!s.isEmpty() && (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.')) {
//先删去p的前两个元素检验(当前p的前两个字符可能匹配不到任何的s情况)
if (isMatch(s, p.substring(2, p.length()))) {
return true;
}
//前面的while条件逐一判断s[0]与p[0]是否相等
s = s.substring(1, s.length());
}
//说明p的前两个字符已经匹配完毕,p取后面的字符
return isMatch(s,p.substring(2));
}
}
Runtime: 94 ms, faster than 5.02% of Java online submissions for Regular Expression Matching.
Memory Usage: 44 MB, less than 9.09% of Java online submissions forRegular Expression Matching.
显然太慢了。
2.2 动态规划
之前在看最长子串的时候,就是动态规划。
到这个问题上,怎么传递状态又卡住了,这也是我比较虚的一点,觉得看明白了,写代码调不出来。
p[j-1]是正常字符串中的字符位置,因为动态规划多加了一个空的占位符。
看了老印的视频:https://www.youtube.com/watch?v=l3hda49XcDE
可以自己动手画图来理解。
//动态规划
public static boolean isMatch(String s, String p) {
if (s == null || p == null) {
return false;
}
int m = s.length(), n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int i = 1; i < n; i++) { // 初始化第一行,p匹配s = ""
if (p.charAt(i) == '*' && dp[0][i - 1]) {
dp[0][i + 1] = true;
}
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
// p[j - 1]不是"*"的情况,单字符匹配
if(s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '.'){
dp[i][j] = dp[i-1][j-1];
}
else if (p.charAt(j - 1) == '*') {
//*前面字符匹配0次
if (p.charAt(j - 2) != s.charAt(i - 1) && p.charAt(j - 2) != '.') {
dp[i][j] = dp[i][j - 2];
} else {
/*
* dp[i][j] = dp[i-1][j] // in this case, a* counts as multiple a
* dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
* dp[i][j] = dp[i][j-2] // in this case, a* counts as empty
*/
dp[i][j] = dp[i][j - 2] || dp[i][j - 1] || dp[i - 1][j];
}
}
}
}
return dp[m][n];
}
如果对于p.charat[j-1]=='*" 觉得别扭,可以这样换个写法:
dp[i][j] = dp[i][j - 2];
if (p.charAt(j - 2) == s.charAt(i-1) || p.charAt(j - 2) == '.'){
dp[i][j] = dp[i][j - 2] || dp[i][j - 1] || dp[i - 1][j];
}
下面的不能去掉dp[i][j - 2] ||,
不然这种case过不了:
"aasdfasdfasdfasdfas"
"aasdf.*asdf.*asdf.*asdf.*s"