问题描述
给定一个字符串 (s) 和一个字符模式 §。实现支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符。
‘*’ 匹配零个或多个前面的元素。
匹配应该覆盖整个字符串 (s) ,而不是部分字符串。
说明:
- s 可能为空,且只包含从 a-z 的小写字母。
- p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
示例 1:
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。
示例 2:
输入:
s = “aa”
p = “a*”
输出: true
解释: ‘*’ 代表可匹配零个或多个前面的元素, 即可以匹配 ‘a’ 。因此, 重复 ‘a’ 一次, 字符串可变为 “aa”。
示例 3:
输入:
s = “ab”
p = "."
输出: true
解释: “.*” 表示可匹配零个或多个(’’)任意字符(’.’)。
示例 4:
输入:
s = “aab”
p = “c*a*b”
输出: true
解释: ‘c’ 可以不被重复, ‘a’ 可以被重复一次。因此可以匹配字符串 “aab”。
示例 5:
输入:
s = “mississippi”
p = “mis*is*p*.”
输出: false
我的答案
思想
首先可以分为两种情况
p的下一个字符不为"*",直接匹配两个字符,若不想等或者p的当前字符不为零则直接返回false
p的下一个字符为"*",匹配两个字符若相等则s的指针后移一位,若不想等则p的指针后移一位(*可以匹配零个字符),若相等则s的指针后移一位继续匹配
代码
class Solution {
public boolean isMatch(String s, String p) {
if (p.isEmpty()){
return s.isEmpty();
}
int i = 0;
int j = 0;
while(i != s.length() && j != p.length()){
if(s.charAt(i) == (p.charAt(j))){
i++;
j++;
}else{
if(p.charAt(j) == '.'){
i++;
j++;
}else if(p.charAt(j) == '*'&&j>0){
if(p.charAt(j-1) == s.charAt(i)||(p.charAt(j-1)=='.')){
i++;
if(i==s.length()){
j++;
}
}else{
j++;
}
}else if(j<(p.length()-1)&&p.charAt(j+1)=='*'){
j++;
}else{
return false;
}
}
}
if(j<(p.length())&&p.charAt(j)==s.charAt(i-1)){
j++;
}
if(i<s.length()||j<p.length()){
return false;
}
return true;
}
}
问题
大部分的字符串都可以匹配,但是例如s=“aaa”,p="a*a"就不可以,因为j无法向后移动,如果s=“aaaaaa”,p=“a*aaa”,也无法得出正确的结论,因为无法判断
*应该匹配几个a。
递归
官方给出的第一个解法,类似于我自己的答案,也可以说是暴力法
思想
- 设置一个变量用于匹配第一个字符,若匹配成功则置true,若不成功则置false
- 如果p的length>=2以及p的下一个字符为"*",若上面的变量为false截取p从两位后开始,若上面变量为true,则截取s从一位后开始,返回递归
- 若p的下一个字符不为"*"以及变量为true,则截取s和p一位后开始递归
代码
class Solution {
public boolean isMatch(String text, String pattern) {
if (pattern.isEmpty()) return text.isEmpty();
boolean first_match = (!text.isEmpty() &&
(pattern.charAt(0) == text.charAt(0) || pattern.charAt(0) == '.'));
if (pattern.length() >= 2 && pattern.charAt(1) == '*'){
return (isMatch(text, pattern.substring(2)) ||
(first_match && isMatch(text.substring(1), pattern)));
} else {
return first_match && isMatch(text.substring(1), pattern.substring(1));
}
}
}
动态规划
思想
- 初始化数组,dp[s.length()+1][p.length[]+1],用于存储子问题结果
- 两层循环,外面循环s,里面循环p
- 第一次判断,与上面的相同,看是否相等或者p为
“.”,记录变量。 - 如果s的下一个为"*",且记录的变量为false,则记录d[i][j]为d[i][j+2],若为true记录为d[i+1][j]
- 如果不为"*",则直接记录d[i][j]为d[i+1][j+1],遍历完成返回dp[0][0]
代码
class Solution {
public boolean isMatch(String text, String pattern) {
boolean[][] dp = new boolean[text.length() + 1][pattern.length() + 1];
dp[text.length()][pattern.length()] = true;
for (int i = text.length(); i >= 0; i--){
for (int j = pattern.length() - 1; j >= 0; j--){
boolean first_match = (i < text.length() &&
(pattern.charAt(j) == text.charAt(i) ||
pattern.charAt(j) == '.'));
if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
dp[i][j] = dp[i][j+2] || first_match && dp[i+1][j];
} else {
dp[i][j] = first_match && dp[i+1][j+1];
}
}
}
return dp[0][0];
}
}
总结
基本上所有的算法判断的思路都是一样的,不同的是处理的方式,递归方式一般是不推荐使用的,不停的调用函数很浪费时间,而这个问题属于比较典型的动态规划,判断完一个字符判断下一个,而时间也比递归 要少得多。