题目
给定一个字符串 (s
) 和一个字符模式 (p
)。实现支持 '.'
和 '*'
的正则表达式匹配。
'.' 匹配任意单个字符。
'*' 匹配零个或多个前面的元素。
匹配应该覆盖整个字符串 (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
题解
无官方题解,下面贴我自己的解答
执行用时 : 137 ms, 在Regular Expression Matching的Java提交中击败了37.10% 的用户
内存消耗 : 43.6 MB, 在Regular Expression Matching的Java提交中击败了62.24% 的用户
主要用的递归解决的,感觉很多地方在重复计算,有点蠢。所以跑得很慢。
可以使用动态规划优化一下的,但做了一晚上没什么心情了,先贴一份递归法解决的,日后有机会再优化了更新一下blog。
class Solution {
public boolean isMatch(String s, String p) {
if("".equals(s)||"".equals(p)){
return dealWithEnd(s,p);
}
if(p.charAt(0)=='.'){
if(p.length()>1&& p.charAt(1)=='*'){
return pointStarMatch(s,p);
}
else{
return isMatch(s.substring(1),p.substring(1));
}
}
else {
if(p.length()>1&& p.charAt(1)=='*'){
return characterStarMatch(s,p);
}
else{
return singleMatch(s,p);
}
}
}
boolean singleMatch(String s,String p){
//System.out.println("检查单个字母"+s.charAt(0));
if(s.charAt(0)==p.charAt(0)) return isMatch(s.substring(1),p.substring(1));
else return false;
}
boolean characterStarMatch(String s,String p){
//System.out.println("检查字母加星号"+p.charAt(0)+"*");
char p0=p.charAt(0);
if("".equals(s)) return dealWithEnd(s,p);
if(s.charAt(0)==p0){
if(characterStarMatch(s.substring(1),p)||isMatch(s,p.substring(2))){
return true;
}
}
else{
if(isMatch(s,p.substring(2))){
return true;
}
}
return false;
}
boolean pointStarMatch(String s,String p){
if("".equals(s)) return dealWithEnd(s,p);
if(isMatch(s,p.substring(2))||pointStarMatch(s.substring(1),p)) return true;
return false;
}
boolean dealWithEnd(String aS, String aP){
if((!"".equals(aS) && "".equals(aP))) return false;
else{
if("".equals(aS)&&!"".equals(aP)){
if(aP.length()%2==0){
int pos=1;
while(pos<aP.length()){
if(aP.charAt(pos)!='*') return false;
pos+=2;
}
return true;
}
else return false;
}
else return true;//aS和aP全部是空的
}
}
}
一开始自己有些思路都是从底下想按动态规划备忘录方法往上走的,但走着走着自己思路就糊了。。。
递归法还是比较直观,从上往下。但是自己还是会傻乎乎地想用循环,其实改改递归条件就可以了。循环应该是动归备忘录才要。
查了一下其他人的方法,发现递归这种其实按算法的说法应该叫做回溯法,只是实现用到了递归。也有非递归的回溯实现。
这里贴一份感觉比较简单明了的递归回溯法(来源:LeetCode:10 正则表达式匹配(Java) - 随我的博客 - CSDN博客 ):
public 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) == '*') {
//看有没有可能,剩下的pattern匹配上全部的text
//看有没有可能,剩下的text匹配整个pattern
//isMatch(text, pattern.substring(2)) 指当p第二个为*时,前面的字符不影响匹配所以可以忽略,所以将*以及*之前的一个字符删除后匹配之后的字符,这就是为什么用pattern.substring(2)
//如果第一个已经匹配成功,并且第二个字符为*时,这是我们就要判断之后的需要匹配的字符串是否是多个前面的元素(*的功能),这就是first_match && isMatch(text.substring(1), pattern))的意义
return (isMatch(text, pattern.substring(2)) ||
(first_match && isMatch(text.substring(1), pattern)));
} else {
//没有星星的情况:第一个字符相等,而且剩下的text,匹配上剩下的pattern,没有星星且第一个匹配成功,那么s和p同时向右移动一位看是否仍然能匹配成功
return first_match && isMatch(text.substring(1), pattern.substring(1));
}
}
}
此外还有动态规划的方法(来源,力扣评论区代码link 解析link):
【解析】直接上动态方程:
-
p[j] == s[i]:dp[i][j] = dp[i-1][j-1]
//这个可以和下面“.”的合并 -
p[j] == ".":dp[i][j] = dp[i-1][j-1]
-
p[j] =="*":
3.1
p[j-1] != s[i]:dp[i][j] = dp[i][j-2]
//感觉这里可以和下面没有a的情况合并3.2
p[i-1] == s[i] or p[i-1] == ".":
dp[i][j] = dp[i-1][j] // 多个a的情况
or dp[i][j] = dp[i][j-1] // 单个a的情况
//单个a也可以看做多个a的特例,所以也可以合并or dp[i][j] = dp[i][j-2] // 没有a的情况
class Solution {
public boolean isMatch(String s, String p) {
int sLen = s.length(), pLen = p.length();
boolean[][] memory = new boolean[sLen+1][pLen+1];//这里都加了1,以符合一般人类的想法。所以和前面贴的另一个人的解析会差1
memory[0][0] = true;
//i指示的是s的字符数
for(int i = 0; i <= sLen; i++) {
//j指示的是p的字符数
for(int j = 1; j <= pLen; j++) {
//p[j] =="*"的情况下
if(p.charAt(j-1) == '*') {
memory[i][j] = memory[i][j-2] /*不匹配的情况*/
|| (i > 0 && (s.charAt(i-1) == p.charAt(j-2) || p.charAt(j-2) == '.') /*p[i-1] == s[i] or p[i-1] == "." */ && memory[i-1][j]);/*多个匹配的情况*/
}
//else中是p[j]==s[i]和p[j]=='.'合并的情况。
else {
memory[i][j] = i > 0 /*防止越界*/
&& (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '.') /*p[j]==s[i]和p[j]=='.'*/
&& memory[i-1][j-1];/*真正的状态方程条件*/
}
}
}
return memory[sLen][pLen];
}
}
在上面方法的基础上,由于每次都只使用了memory表中相邻的两行,因此可以进一步降低代码的空间复杂度如下:
class Solution {
public boolean isMatch(String s, String p) {
int sLen = s.length(), pLen = p.length();
boolean[][] memory = new boolean[2][pLen+1];
memory[0][0] = true;
int cur = 0, pre = 0;//cur和pre用来切换使用的二维数组行数。
for(int i = 0; i <= sLen; i++) {
cur = i % 2;//之前的pre不再使用,用来装现在的数据。
pre = (i + 1) % 2;//之前的cur变成了pre
if(i > 1) {
for(int j = 0; j <= pLen; j++) {
memory[cur][j] = false;//将当前要计算的表要占据的位置全部置空,准备接受数据
}
}
for(int j = 1; j <= pLen; j++) {
if(p.charAt(j-1) == '*') {
memory[cur][j] = memory[cur][j-2]
|| (i > 0 && (s.charAt(i-1) == p.charAt(j-2) || p.charAt(j-2) == '.')
&& memory[pre][j]);//这里和前面分析一样,p[i-1] == s[i] or p[i-1] == "."下,不匹配或多匹配的状态方程
}else {
memory[cur][j] = i > 0
&& (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '.')
&& memory[pre][j-1];//这里也差不多,p[j]==s[i]和p[j]=='.'的状态方程
}
}
}
return memory[cur][pLen];
}
}
感想
贴一下自己调试的代码吧,写的也挺蠢的,全是static。。。
开头的注释都是bug过的案例。主要就是递归那边判定条件有点难搞,所以调了一晚上。。。做了差不多3h,提交了16次。。。自己还是太菜了
public class SublimeTextTest{
public static void main(String[] args){
String s = "a"; //"aaa";//"ab"; //"mississippi";//"aa"//"ab"//"aab"
String p = ".*..a*"; //"a*a";//".*c";//"mis*is*p*." //"a*"//".*"//"c*a*b"
boolean output = Solution.isMatch(s,p);
System.out.println(output);
}
}
class Solution {
static public boolean isMatch(String s, String p) {
if("".equals(s)||"".equals(p)){
return dealWithEnd(s,p);
}
if(p.charAt(0)=='.'){
if(p.length()>1&& p.charAt(1)=='*'){
return pointStarMatch(s,p);
}
else{
return isMatch(s.substring(1),p.substring(1));
}
}
else {
if(p.length()>1&& p.charAt(1)=='*'){
return characterStarMatch(s,p);
}
else{
return singleMatch(s,p);
}
}
}
static boolean singleMatch(String s,String p){
System.out.println("检查单个字母"+s.charAt(0)+":"+s+","+p);
if(s.charAt(0)==p.charAt(0)) return isMatch(s.substring(1),p.substring(1));
else return false;
}
static boolean characterStarMatch(String s,String p){
System.out.println("检查字母加星号"+p.charAt(0)+"*"+":"+s+","+p);
char p0=p.charAt(0);
if("".equals(s)) return dealWithEnd(s,p);
if(s.charAt(0)==p0){
if(characterStarMatch(s.substring(1),p)||isMatch(s,p.substring(2))){
return true;
}
}
else{
if(isMatch(s,p.substring(2))){
return true;
}
}
return false;
}
static boolean pointStarMatch(String s,String p){
System.out.println("检查.*"+":"+s+","+p);
if("".equals(s)) return dealWithEnd(s,p);
if(isMatch(s,p.substring(2))||pointStarMatch(s.substring(1),p)) return true;
return false;
}
static boolean dealWithEnd(String aS, String aP){
System.out.println("处理空情况"+":"+aS+","+aP);
if((!"".equals(aS) && "".equals(aP))) return false;
else{
if("".equals(aS)&&!"".equals(aP)){
if(aP.length()%2==0){
int pos=1;
while(pos<aP.length()){
if(aP.charAt(pos)!='*') return false;
pos+=2;
}
return true;
}
else return false;
}
else return true;//aS和aP全部是空的
}
}
}
感觉自己还是需要继续努力啊。动态规划算法感觉还是只能看出可以用,但不太会自己从零开始构建。回溯法也是一堆重复代码,不懂得归纳方法间的相似情况。