【Leetcode】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'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:

输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:

输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:

输入:
s = "mississippi"
p = "mis*is*p*."
输出: false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/regular-expression-matching
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法

 如果p中没有*这个字符,可以对s和p每一位分别进行比较。已当前比较到索引i为例,s[i] != p[i] && p[i] != '.',则此字符串不匹配。但是当p中包含*这个字符时,*前面的字符可以出现0次或者多次,所以我们可以分别判断出现0次或者出现多次分开讨论。下面以sl表示s的长度,pl表示p的长度。

slpl是否匹配
00true
>00false
01false
>01先判断s[0]和p[0],然后递归比较s[1-]和p[1-],1-表示从索引1开始到最后的字符
0>=2判断p[1]是否为*,如果不为*,则false,如果为*,则递归s和p[2-]
>0>=2判断p[1]是否为*,如果不为*,则判断先判断s[0]和p[0],然后递归比较s[1-]和p[1-];如果为*,则判分别递归s,p[2-]以及s[1-]和p[2-],只要这两个任何一个为True ,则s和p匹配

从上面的表格看出,在递归时,有很多重复计算,所以增加备忘录,其实是一种从顶向下的动态规划解法,时间复杂度为O(M*N),空间复杂度为O(M*N),M和N分别为字符串长度。

class Solution {
    //  matchFlag[i]][j]表示i:和j:是否匹配
    public boolean matchFlag[][];
    public boolean isMark[][];
    public boolean isMatch(String s, String p){
        matchFlag = new boolean[s.length()+1][p.length()+1];
        isMark = new boolean[s.length()+1][p.length()+1];
        return dp(s, p, 0, 0);
    }

    public boolean dp(String s, String p, int i, int j) {
        int slength = s.length() - i;
        int plength = p.length() - j;

        if(isMark[i][j] == true){
            return matchFlag[i][j];
        }

        boolean rs = false;
        if(plength == 0){
            rs = slength == 0;
        }

        if (plength == 1){
                rs = ((slength > 0) && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') && dp(s, p, i+1, j+1));
        }

        if(plength >= 2){
            if(p.charAt(j+1) == '*'){
                rs = this.dp(s, p, i, j + 2) 
            || ((slength > 0) && ((s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') && dp(s, p, i+1, j)));
            }else{
                rs = (slength > 0) && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') && dp(s, p, i+1, j+1);
            }
        }
        isMark[i][j] = true;
        matchFlag[i][j] = rs;
        return rs;
    }
}

 

将上述算法改写一下,可以得到从底向上的的动态规划解法,时间复杂度为O(M*N),空间复杂度为O(M*N),M和N分别为字符串长度。

class Solution {
    public boolean isMatch(String s, String p){
        int slength = s.length();
        int plength = p.length();
        boolean dp[][] = new boolean[slength+1][plength+1];
        dp[slength][plength] = true; 

        for(int i = slength; i >= 0; i--){
            for(int j = plength; j >= 0; j--){
                if(j  == plength){
                    dp[i][j] = i == slength;
                    continue;
                }
                boolean firstMatch = slength-i > 0 && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
                if(plength - j == 1){
                    dp[i][j] = firstMatch && dp[i+1][j+1];
                }else{
                    if(p.charAt(j + 1) == '*'){
                        dp[i][j] = dp[i][j+2]  || (firstMatch && dp[i+1][j]);
                    }else{
                        dp[i][j] = firstMatch && dp[i+1][j+1];
                    }
                }
            }
        }

        return dp[0][0];
    }
}

然后dp[i][j]只和dp[i+1][j+1] dp[i][j+2] dp [i+1][j]相关,所以可以再次减少空间复杂度,空间复杂度为O(N), N为p的长度。

class Solution {
    public boolean isMatch(String s, String p){
        int slength = s.length();
        int plength = p.length();
        boolean dp[][] = new boolean[2][plength+1];
        int cur = 0;
        for(int i = slength; i >= 0; i--){
            int pre = (cur + 1) % 2;
            for(int j = plength; j >= 0; j--){
                if(j  == plength){
                    dp[cur][j] = i == slength;
                    continue;
                }
                boolean firstMatch = slength-i > 0 && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
                if(plength - j == 1){
                    dp[cur][j] = firstMatch && dp[pre][j+1];
                }else{
                    if(p.charAt(j + 1) == '*'){
                        dp[cur][j] = dp[cur][j+2]  || (firstMatch && dp[pre][j]);
                    }else{
                        dp[cur][j] = firstMatch && dp[pre][j+1];
                    }
                }
            }

            cur = (cur + 1) % 2;
        }

        return dp[(cur + 1) % 2][0];
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
正则表达式匹配是一个经典的算法问题,主要是判断一个字符串是否能够完全匹配给定的正则表式。 在LeetCode上,也有一道关于正则表达式匹配的题目,题目编号是10。这道题目要求实现一个支持 '.' 和 '*' 的正则表达式匹配,其中 '.' 可以匹配任意单个字符,'*' 可以匹配零个或多个前面的元素。 解决这道题可以使用动态规划的思想,具体的思路如下: 1. 创建一个二维数组dp,dp[i][j]表示s的前i个字符与p的前j个字符是否匹配。 2. 初始化dp为true,表示空字符串与空正则表达式是匹配的。 3. 初始化dp[i]为false,表示非空字符串与空正则表达式是不匹配的。 4. 初始化dp[j],如果p[j-1]是"*",则dp[j]的值取决于dp[j-2]的值,表示将p[j-2]与p[j-1]去掉后的正则表达式是否匹配空字符串。 5. 对于其它的dp[i][j],分成两种情况: - 如果p[j-1]是"."或者与s[i-1]相等,则dp[i][j]的值取决于dp[i-1][j-1]的值,表示将s[i-1]和p[j-1]去掉后的字符串是否匹配。 - 如果p[j-1]是"*",则dp[i][j]的值取决于以下两种情况: - dp[i][j-2]的值,表示将p[j-2]和p[j-1]去掉后的正则表达式是否匹配s的前i个字符。 - dp[i-1][j]的值,表示将s[i-1]与p[j-2]匹配后的字符串是否匹配p的前j个字符。 6. 最后返回dp[s.length()][p.length()]的值,表示整个字符串s与正则表达式p是否完全匹配。 以上是一种使用动态规划解决正则表达式匹配问题的思路,具体的实现可以参考LeetCode官方提供的递归思路的解法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [LeetCode算法 —— 正则表达式匹配(详解官方动态规划思想)](https://blog.csdn.net/weixin_42100963/article/details/106953141)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值