剑指 Offer 19. 正则表达式匹配

本人小扒菜,写的不好大家多多指导,话不多说上题目。

请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。

示例 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
  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'

解题思路
1、两种方法求解:
(一)、递归,根据p[pi]的下一位p[pi+1]是否为''来进行可能性划分,确保下一次函数调用时p[pi]不会为'',这样递归不会产生后效性的问题;
(二)、将前面递归的方法改成dp填表,注意填表时不需要考虑p[pi]为'*’的情况;

代码:

class Solution {
    public boolean isMatch(String s, String p) {
        // base case
        if (s == null || p == null) {
            return false;
        }

        char[] sArr = s.toCharArray();
        char[] pArr = p.toCharArray();

        int kuan = sArr.length;
        int chang = pArr.length;

        boolean[][] dp = new boolean[kuan+1][chang+1];

        return dpMatch(sArr, pArr, dp);

//        return recurMatch(sArr, pArr, 0, 0);

    }

    // 递归解
    public boolean recurMatch(char[] s, char[] p, int si, int pi) {
        // base case
        if (pi == p.length) {
            return si == s.length;
        }

        // pi的后一位不是'*'
        // 这里就考虑来pi处于字符p最后一位的情况,那么整个递归pi始终不可能为*,后面递归改写为填表的时候,就可以利用这个性质
        if (pi+1 == p.length || p[pi+1] != '*') {
            return si != s.length
                    && (s[si] == p[pi] || p[pi] == '.')
                    && recurMatch(s, p, si+1, pi+1);
        }

        // pi的后一位是'*'
        // 当p[pi]和s[si]相等的时候
        // 循环遍历pi和pi+1('*')合起来对应s不同数量字符时的情况,直到无法满足不了循环条件退出
        while (si != s.length && (s[si] == p[pi] || p[pi] =='.')) {
            if (recurMatch(s, p, si, pi+2)) {
                return true;
            }
            si++;
        }

        // pi的后一位是'*'
        // 当p[pi]和s[si]不相等的时候
        return recurMatch(s, p, si, pi+2);
    }

    public boolean dpMatch(char[] s, char[] p, boolean[][] dp) {
        // init dp[][]
        int sLen = s.length;
        int pLen = p.length;

        // p[pi]和s[si]都越界为空的时候
        dp[sLen][pLen] = true;

        // 最后一列,p[pi]越界为空了
        for (int i = 0; i < s.length - 1; i++) {
            dp[i][pLen] = false;
        }

        // 最后一行,s[si]越界为空了
        for (int i = p.length-2; i >=0 ; i=i-2) {
            if (p[i] != '*' && p[i+1] == '*') {
                dp[sLen][i] = true;
            } else {
                break;
            }
        }

        // 倒数第二列,p[pi]是最后一位
        if (sLen >0 && pLen >0) {
            if (p[pLen-1] == '.' || p[pLen-1] == s[sLen-1]) {
                dp[sLen-1][pLen-1] = true;
            }
        }


        // --------*初始化结束,开始递归过程转dp填表*----------
        // 从下到上,从右到左的顺序


        for (int curR = sLen-1; curR >=0 ; curR--) {
            for (int curC = pLen-2; curC>=0; curC--) {
                // p[pi+1]的值不等于'*'
                if (p[curC+1] != '*') {
                    dp[curR][curC] = (s[curR] == p[curC] || p[curC] == '.')
                            && dp[curR+1][curC+1];
                } else {        // p[pi+1]的值等于'*'

                    // 当p[pi]和s[si]相等的时候
                    int si = curR;

                    while (si != sLen && (s[si] == p[curC] || p[curC] == '.')) {
                        if (dp[si][curC+2]) {
                            dp[curR][curC] = true;
                            break;
                        }
                        si++;
                    }

                    // 当p[pi]和s[si]不相等的时候
                    if (dp[curR][curC] != true) {
                        dp[curR][curC] = dp[si][curC+2];
                    }
                }

            }
        }


        return dp[0][0];
    }
}

复杂度分析

时间复杂度:O(mn)O(mn),其中 mm 和 nn 分别是字符串 ss 和 pp 的长度。我们需要计算出所有的状态,并且每个状态在进行转移时的时间复杂度为 O(1)O(1)。

空间复杂度:O(mn)O(mn),即为存储所有状态使用的空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值