leetcode题解-10.正则表达式匹配


博客专栏地址:https://blog.csdn.net/feng964497595/category_9848847.html
github地址:https://github.com/mufeng964497595/leetcode


题目描述

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

  • ‘.’ 匹配任意单个字符
  • ‘*’ 匹配零个或多个前面的那一个元素
    所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

说明:

  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。

思路解析

1 递归解法

  1. 这题目一出来,看着就可以照着题目意思模拟进行,不过遇到*号,则要判断是取0次、1次还是多次。对于这种多决策问题,就很容易想到动态规划。不过动态规划要推公式需要些灵感,灵感没来的话公式就很难推,那么我们怎么办呢?有个土方法,就是用递归。你可能会说递归很容易出现重复计算导致超时,这个问题咱先不考虑,先确认一下递归可不可行,再来解决重复计算的问题。
  2. 匹配串和正则串咱一个个字符进行比较,记匹配串当前匹配的下标是i,正则串当前匹配的下标是j。
    2.1 如果两个字符相同,或者正则串对应的字符是’.’,那么就认为当前字符匹配成功啦。这时候咱看看正则串的下一个字符是不是’*’。如果是’*’,咱就分别递归处理*取0次(i不变,j+2)和多次(i+1,j不变)这三种情况。如果不是’*’,那就简单啦,直接i+1,j+1
    2.2 如果2.1不成立,但是j+1对应的字符是’*’,那么很显然,把‘*’取0次再看看剩下的能不能匹配上。即i不变,j+2
    2.3 如果2.1、2.2都不成立,那么很显然,直接就是匹配不上
  3. 主体流程列出来后,看着没啥问题,那么接下来就是处理递归的边界问题了,避免递归停不下来。记匹配串的下标是i,长度是len1;正则串的下标是j,长度是len2
    3.1 如果i >= len1 && j < len2,此时匹配串已经空了,但是正则串还有,那么就要把正则串的那些’*‘都看作是取0次,看看能不能匹配上。如果正则串的j+1是’*’,就i不变,j+2进行递归匹配。如果j+1不是’*’,那么就表示匹配失败。
    3.2 如果i >= len1 && j >= len2,即两个串都匹配完了,那么就表示匹配成功
    3.3 如果i < len1 && j >= len2,即匹配串还有,正则串已经空了,那么肯定就是匹配失败
  4. 整个递归流程都ok了,那么接下来就要处理递归重复计算导致超时。我们对比一下递归和动态规划,其实动态规划不容易超时的原因就是因为动态规划有保存中间计算出来的结果。那么我们也用一个二维数组来保存递归过程的中间结果,是不是就差不多了呢嘿嘿嘿。我以前动态规划推不出来公式的时候就是这样玩的哈哈哈。
  5. 保存中间结果的二维数组的大小要开多大呢?题目没说两个字符串的长度是多少,不过按照经验,一般这种字符串题目的字符串长度不会很长,开个1005一般就差不多了(感觉这里会被吐槽。。。)

2 动态规划解法

  1. 递归解法都写出来了,那么动态规划咱也不需要推公式了,只需要把递归的函数改为数组去替代就可以了。改写完之后,相比于递归解法,少了函数调用压栈出栈的过程,耗时会相对短一些~
  2. 看代码比对一下两种解法的代码就能看出两者的关系。

示例代码

1 递归解法

class Solution {
public:
    bool isMatch(const string& s, const string& p) {
        memset(ans, -1, sizeof(ans));
        return dp(0, 0, s.length(), p.length(), s, p);
    }

private:
    bool dp(int i, int j, int len1, int len2, const string& s, const string& p) {
        // 已计算过,不再重复计算,直接返回结果
        if (ans[i][j] != -1) return ans[i][j];

        // 正则串已结束,看匹配串空不空来确定匹配结果
        if (j >= len2) return ans[i][j] = i >= len1;

        if (i >= len1 && j < len2) {
            // 匹配串已结束,正则串还有,则把带有*的跳过看看是否能满足
            if (j+1 < len2 && p[j+1] == '*')
                return ans[i][j] = dp(i, j+2, len1, len2, s, p);
            return ans[i][j] = false;
        }

        bool res = false;
        if (s[i] == p[j] || p[j] == '.') {  // 当前字符匹配成功
            if (j+1 >= len2 || p[j+1] != '*') {
                // 下一个字符不是*,两个串都直接校验下一个字符
                res = dp(i+1, j+1, len1, len2, s, p);
            } else {
                // 下一个字符是*,分0、多这两种情况进行匹配
                res = dp(i+1, j, len1, len2, s, p) || dp(i, j+2, len1, len2, s, p);
            }
        } else if (j+1 < len2 && p[j+1] == '*'){
            // 当前字符匹配失败,但下一个字符是*,则把*当作取0次进行匹配
            res = dp(i, j+2, len1, len2, s, p);
        } else {
            res = false;
        }

        return ans[i][j] = res;
    }

private:
    int ans[1005][1005];    // 保存结果
};

2 动态规划解法

class Solution {
public:
    bool isMatch(const string& s, const string& p) {
        bool dp[1005][1005];    // dp[i][j]表示s[i:]与p[j:]的匹配结果
        memset(dp, 0, sizeof(dp));
        int len1 = s.length(), len2 = p.length();
        dp[len1][len2] = true;  // 两个串都为空

        for (int i = len1; i >= 0; --i) {
            // i从len1开始,是因为s是空串时也有可能匹配成功
            for (int j = len2 - 1; j >= 0; --j) {
                // j从len1开始,是因为p是空串时,除非s是空串,否则都匹配失败
                // 前面已设置dp[len1][len2]=true,已包含该逻辑,不需要再特殊处理
                if (s[i] == p[j] || p[j] == '.') {  // 当前字符匹配成功
                    if (j+1 >= len2 || p[j+1] != '*') {
                        // 下一个字符不是*,匹配结果取决于两个串的下一个字符
                        dp[i][j] = dp[i+1][j+1];
                    } else {
                        // 下一个字符是*,分0、多次这两个情况进行匹配
                        dp[i][j] = dp[i+1][j] || dp[i][j+2];
                    }
                } else if (j+1 < len2 && p[j+1] == '*') {
                    // 当前字符匹配失败,但下一个字符是*,则把*当作取0次进行匹配
                    dp[i][j] = dp[i][j+2];
                } else {
                    dp[i][j] = false;
                }
            }
        }

        return dp[0][0];
    }
};

后记

动态规划公式推导不出来,用递归土方法再转为动态规划,速度可能比硬推公式要更快哟

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值