【力扣算法】10-正则表达式匹配

题目

给定一个字符串 (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):

【解析】直接上动态方程:

  1. p[j] == s[i]:dp[i][j] = dp[i-1][j-1]//这个可以和下面“.”的合并

  2. p[j] == ".":dp[i][j] = dp[i-1][j-1]

  3. 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全部是空的
        }
    }
}

感觉自己还是需要继续努力啊。动态规划算法感觉还是只能看出可以用,但不太会自己从零开始构建。回溯法也是一堆重复代码,不懂得归纳方法间的相似情况。

猜你喜欢

转载自blog.csdn.net/ZeromaXHe/article/details/89408238