剑指offer 19 正则表达式匹配

@date: 8/18/2020 10:26:00 AM
@难度: 困难
考察内容: 字符串 动态规划
@e-mail: lwyz521604#163.com
题目来自《剑指offer》 电子工业出版社

请实现一个函数用来匹配包含'. ''*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含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 的小写字母以及字符 . 和 ,无连续的 '’。

递归解法

从示例来看,题目中的.*指的应该是任意的字符串,而不是只含有相同字符的字符串。这道题可以通过逐个判断模式字符串中前两个字符的情况,然后递归地来解:

class Solution {
    public boolean isMatch(String s, String p) {

        return this.arrayMatch(s.toCharArray(), p.toCharArray(), 0, 0);
    }

    private boolean arrayMatch(char[] arrS, char[] arrP, int ptrS, int ptrP){
        int n1 = arrS.length;
        int n2 = arrP.length;

        if(ptrP==n2){  // 模式为空时,字符串必须为空
            return n1 == ptrS;
        }

        if(n1-ptrS==0){  // 字符串为空,模式非空时,模式必须是 某* 的形式
            if(n2-ptrP>=2 && arrP[ptrP+1]=='*'){
                return arrayMatch(arrS, arrP, ptrS, ptrP+2);
            }
            return false;
        }

        // 如果模式字符串只剩最后一个位置
        if(n2-ptrP==1){
            if(n1-ptrS==1){
                if(arrP[ptrP]=='.')
                    return true;
                else
                    return arrP[ptrP]==arrS[ptrS];
            }
            return false;
        }

        // 模式字符串还剩下两位或超过两位的情况
        if(arrP[ptrP]=='.'){
            if(arrP[ptrP+1]=='*'){
                // 题目对这种情况的意思似乎是任意的字符出现任意次
                return arrayMatch(arrS, arrP, ptrS+1, ptrP+2)
                        || arrayMatch(arrS, arrP, ptrS, ptrP+2)
                        || arrayMatch(arrS, arrP, ptrS+1, ptrP);
            }else{
                return arrayMatch(arrS, arrP, ptrS+1, ptrP+1);
            }
        }else{
            if(arrP[ptrP+1]=='*'){
                if(arrS[ptrS]==arrP[ptrP]){
                    return arrayMatch(arrS, arrP, ptrS+1, ptrP+2)
                            || arrayMatch(arrS, arrP, ptrS, ptrP+2)
                            || arrayMatch(arrS, arrP, ptrS+1, ptrP);
                }else{
                    return arrayMatch(arrS, arrP, ptrS, ptrP+2);
                }
            }else{
                if(arrS[ptrS]==arrP[ptrP]){
                    return arrayMatch(arrS, arrP, ptrS+1, ptrP+1);
                }else{
                    return false;
                }
            }
        }

    }
}

提交结果如下所示,时间效率并不高,原因应该是在递归调用中的几个“或”语句下面存在重复求解子问题的情况,因此可以用一个矩阵将这些子问题的求解结果存储下来,这样也就是动态规划方法了。

执行用时:846 ms, 在所有 Java 提交中击败了5.03%的用户
内存消耗:38.3 MB, 在所有 Java 提交中击败了59.69%的用户

自上而下的动态规划解法

前面已经提到,递归解法存在重复求解子问题的情况,因而可以在递归过程中增加备忘机制,使得时间效率大幅提高。《算法导论》中称这种思路为自上而下带备忘的动态规划方法。其具体实现如下,由于直接在上一个版本的基础上略加修改而成的,代码的书写不太优美,其实可以让arrayMatch函数变成void的。

class Solution {
    public boolean isMatch(String s, String p) {

        int[][] count = new int[s.length()+1][p.length()+1];
        return this.arrayMatch(s.toCharArray(), p.toCharArray(), 0, 0, count);

    }

    private boolean arrayMatch(char[] arrS, char[] arrP, int ptrS, int ptrP, int[][] count){
        int n1 = arrS.length;
        int n2 = arrP.length;

        if(count[ptrS][ptrP]==1)
            return true;
        if(count[ptrS][ptrP]==-1)
            return false;

        if(ptrP==n2){  // 模式为空时,字符串必须为空
            if(n1 == ptrS){
                count[ptrS][ptrP] = 1;
                return true;
            }
            count[ptrS][ptrP] = -1;
            return false;
        }

        if(n1-ptrS==0){  // 字符串为空,模式非空时,模式必须是 某* 的形式
            if(n2-ptrP>=2 && arrP[ptrP+1]=='*'){
                boolean temp = arrayMatch(arrS, arrP, ptrS, ptrP+2, count);
                if(temp)
                    count[ptrS][ptrP] = 1;
                else
                    count[ptrS][ptrP] = -1;
                return temp;
            }
            count[ptrS][ptrP] = -1;
            return false;
        }

        // 如果模式字符串只剩最后一个位置
        if(n2-ptrP==1){
            if(n1-ptrS==1){
                if(arrP[ptrP]=='.') {
                    count[ptrS][ptrP] = 1;
                    return true;
                }else{
                    if(arrP[ptrP]==arrS[ptrS]){
                        count[ptrS][ptrP] = 1;
                        return true;
                    }
                    count[ptrS][ptrP] = -1;
                    return false;
                }
            }
            count[ptrS][ptrP] = -1;
            return false;
        }

        // 模式字符串还剩下两位或超过两位的情况
        if(arrP[ptrP]=='.'){
            if(arrP[ptrP+1]=='*'){
                // 题目对这种情况的意思似乎是任意的字符出现任意次
                boolean temp = arrayMatch(arrS, arrP, ptrS+1, ptrP+2, count)
                        || arrayMatch(arrS, arrP, ptrS, ptrP+2, count)
                        || arrayMatch(arrS, arrP, ptrS+1, ptrP, count);
                if(temp)
                    count[ptrS][ptrP] = 1;
                else
                    count[ptrS][ptrP] = -1;
                return temp;
            }else{
                boolean temp = arrayMatch(arrS, arrP, ptrS+1, ptrP+1, count);
                if(temp)
                    count[ptrS][ptrP] = 1;
                else
                    count[ptrS][ptrP] = -1;
                return temp;
            }
        }else{
            if(arrP[ptrP+1]=='*'){
                if(arrS[ptrS]==arrP[ptrP]){
                    boolean temp = this.arrayMatch(arrS, arrP, ptrS+1, ptrP+2, count)
                            || this.arrayMatch(arrS, arrP, ptrS, ptrP+2, count)
                            || this.arrayMatch(arrS, arrP, ptrS+1, ptrP, count);
                    if(temp)
                        count[ptrS][ptrP] = 1;
                    else
                        count[ptrS][ptrP] = -1;
                    return temp;
                }else{
                    boolean temp = arrayMatch(arrS, arrP, ptrS, ptrP+2, count);
                    if(temp)
                        count[ptrS][ptrP] = 1;
                    else
                        count[ptrS][ptrP] = -1;
                    return temp;
                }
            }else{
                if(arrS[ptrS]==arrP[ptrP]){
                    boolean temp = arrayMatch(arrS, arrP, ptrS+1, ptrP+1, count);
                    if(temp)
                        count[ptrS][ptrP] = 1;
                    else
                        count[ptrS][ptrP] = -1;
                    return temp;
                }else{
                    count[ptrS][ptrP] = -1;
                    return false;
                }
            }
        }

    }

}

在系统中提交的结果为

执行用时:2 ms, 在所有 Java 提交中击败了99.91%的用户
内存消耗:39.7 MB, 在所有 Java 提交中击败了20.39%的用户
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值