leetcode:10. 正则表达式匹配

题目来源

题目描述

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    bool isMatch(string s, string p) {
    
    }
};

题目解析

分析题意

  • str中一定不能含有.*
  • exp中:
    • .可以匹配任意一个字符
    • *:必须和exp前一个字符绑定使用(前一个字符可以是.
      • 可以消除前面的那个符号
      • 可以什么也不干
      • 可以复制任意次前面的个符号(1次,2次,3次)
    • 因此exp中不能出现连续的两个*

在这里插入图片描述

因此,在匹配开始之前

先做一个大的过滤:

  • str中一定不能含有.*
  • exp中不能出现连续的两个*

匹配是可以从前往后扫描,也可以从后往前扫描

(1)从前往后扫描的话:字符后面是否跟着*会影响结果,分析起来比较复杂
在这里插入图片描述
(2)从后往前扫描的话

  • *的前面一定有一个字符,*也只影响这一个字符,它就像一个拷贝器

在这里插入图片描述

  • s、p 串是否匹配,取决于:最右端是否匹配、剩余的子串是否匹配。
  • 只是最右端可能是特殊符号,需要分情况讨论而已

样本对应模型

  • 定义dp[si][ei]str[si......]能不能被exp[ei...]匹配出来

base case:

  • exp没有了,str也必须没有了才是对得上
  • exp没有了,但是str还有没有匹配上的,那么就返回false

普通情况:

  • expr有,str没有
  • expr有,str有

怎么决策普通情况呢?根据ei的下一个位置是不是*来决策

  • 如果 e i + 1 ei+1 ei+1不是*
    • e i + 1 ei+1 ei+1不是*有哪些情况呢?
      • e i + 1 = = e . l e n ei + 1 == e.len ei+1==e.len,说明 e i ei ei s i si si是最后的字符了
      • e [ e i + 1 ] ! = ∗ e[ei + 1] != * e[ei+1]!=
    • 怎么决策?
      • 如果 e i + 1 ei+1 ei+1不是*,就说明 e i ei ei没有操作空间了,就是说后面没有*可以将 e i ei ei变没
      • 此时 s i si si必须能和 e i ei ei对上。这意味着:
        • s i ! = s . l e n si != s.len si!=s.len
        • 而且,下面情况之一必须成立:
          • e [ e i ] = = s [ s i ] e[ei] == s[si] e[ei]==s[si]
          • e [ e i ] = = . e[ei] == . e[ei]==.
      • 然后对继续下一个字符匹配: s [ s i + 1 ] 与 e [ e i + 1 ] s[si + 1]与e[ei+1] s[si+1]e[ei+1]
  • 如果 e i + 1 ei+1 ei+1*,那么可以使用* e [ e i ] e[ei] e[ei]消除,复制、什么也不干
    • 如果 e [ e i ] ! = s [ s i ] e[ei] != s[si] e[ei]!=s[si],那么必须用 * e [ e i ] e[ei] e[ei]消除,因此,下一步 s [ s i ] s[si] s[si] e [ e i + 2 ] e[ei+2] e[ei+2]匹配
    • 如果 e [ e i ] = = s [ s i ] e[ei] == s[si] e[ei]==s[si],那么可以用来消除零个或者一个或者全部的 s [ s i ] s[si] s[si]
class Solution {
    bool isValid(string s, string e){
        // s中不能有'.' or '*'
        for (int i = 0; i < s.size(); ++i) {
            if(s[i] == '.' || s[i] == '*'){
                return false;
            }
        }

        // 开头的e[0]不能是'*',没有相邻的'*'
        for (int i = 0; i < e.size(); ++i) {
            if(e[i] == '*' && (i == 0 || e[i - 1] == '*')){
                return false;
            }
        }
        return true;
    }
    // str[si.....] 能不能被 exp[ei.....]配出来! true false
    bool process(string s, string e, int si, int ei){
        if(ei == e.length()){
            return si == s.length();
        }

        // exp[ei]还有字符
        // ei + 1位置的字符,不是*
        if(ei + 1 == e.length() || e[ei + 1] != '*'){
            // ei + 1 不是*
            // str[si] 必须和 exp[ei] 能配上!
            return si != s.length() && (e[ei] == s[si] || e[ei] == '.') && process(s, e, si + 1, ei + 1);
        }

        // exp[ei]还有字符
        // ei + 1位置的字符,是*
        while (si != s.length() && (e[ei] == s[si] || e[ei] == '.')) {
            if (process(s, e, si, ei + 2)) {
                return true;
            }
            si++;
        }
        
        return process(s, e, si, ei + 2);
    }
public:
    bool isMatch(string s, string e) {
        if(e.empty() || e.empty()){
            return false;
        }
        
        return isValid(s, e) && process(s, e, 0, 0);
    }
};

记忆化搜索


class Solution {
    bool isValid(string s, string e){
        // s中不能有'.' or '*'
        for (int i = 0; i < s.size(); ++i) {
            if(s[i] == '.' || s[i] == '*'){
                return false;
            }
        }

        // 开头的e[0]不能是'*',没有相邻的'*'
        for (int i = 0; i < e.size(); ++i) {
            if(e[i] == '*' && (i == 0 || e[i - 1] == '*')){
                return false;
            }
        }
        return true;
    }
    // str[si.....] 能不能被 exp[ei.....]配出来! true false
    bool process(string s, string e, int si, int ei, std::vector<std::vector<int>> &dp){
        if (dp[si][ei] != 0) {
            return dp[si][ei] == 1;
        }
        
        bool ans = false;
        if(ei == e.length()){
            ans = si == s.length();
        }else{
            // exp[ei]还有字符
            // ei + 1位置的字符,不是*
            if(ei + 1 == e.length() || e[ei + 1] != '*'){
                // ei + 1 不是*
                // str[si] 必须和 exp[ei] 能配上!
                ans =  si != s.length() && (e[ei] == s[si] || e[ei] == '.') && process(s, e, si + 1, ei + 1);
            }else{
                if(si == s.length()){
                    ans = process(s, e, si, ei + 2, dp);
                }else{  // si没结束
                    if (s[si] != e[ei] && e[ei] != '.') {
                        ans = process(s, e, si, ei + 2, dp);
                    } else { // s[si] 可以和 e[ei]配上
                        ans = process(s, e, si, ei + 2, dp) || process(s, e, si + 1, ei, dp);
                    }
                }
            }
        }

        dp[si][ei] = ans ? 1 : -1;
        return ans;
    }
public:
    bool isMatch(string s, string e) {
        if(e.empty() || e.empty()){
            return false;
        }
        
        std::vector<std::vector<int>> dp(s.length() + 1, std::vector<int>(e.length() + 1));
        // dp[i][j] = 0, 没算过!
        // dp[i][j] = -1 算过,返回值是false
        // dp[i][j] = 1 算过,返回值是true
        return isValid(s, e) && process(s, e, 0, 0, dp);
    }
};

状态转移方程

(1)定义状态

  • d p [ i ] [ j ] dp[i][j] dp[i][j]:s的前 i i i个字符和 p p p的前 j j j个字符能否匹配

(2)状态转移

在进行状态转移时, s s s中的字符是固定不变的,我们考虑 p p p的第 j j j个字符与 s s s的匹配情况

  1. p [ j ] p[j] p[j]是一个小写字母 a − z a-z az,则 s [ i ] s[i] s[i]必须也是小写字母而且与 p [ j ] p[j] p[j]相同

在这里插入图片描述
2. p [ j ] = = . p[j] == . p[j]==.,则 p [ j ] p[j] p[j]一定可以与 s [ i ] s[i] s[i]匹配成功,此时:

在这里插入图片描述

  1. p [ j ] = = ∗ p[j] == * p[j]==,则 p [ j ] p[j] p[j]可以匹配(复制)其前一个字符 p [ j − 1 ] p[j - 1] p[j1]任意次(包括0次)。

举个例子,以下图 s= "abaaacd",p="aba*cd",s[i]='a',p[j-1]='a'p[j]='*' 为例
在这里插入图片描述

  • 匹配0次,意味着 p [ j − 1 ] p[j - 1] p[j1] p [ j ] p[j] p[j]不起作用,相当于在 p p p中删除了 p [ j − 1 ] p[j - 1] p[j1] p [ j ] p[j] p[j],此时有:

在这里插入图片描述

  • 匹配 1 次,意味着 p [ j − 1 ] p[j - 1] p[j1] p [ j ] p[j] p[j]组成了a,此时,如果 s [ i ] = = p [ j − 1 ] s[i] == p[j - 1] s[i]==p[j1],则 d p [ i ] [ j ] dp[i][j] dp[i][j]可以由 d p [ i − 1 ] [ j − 2 ] dp[i-1][j-2] dp[i1][j2]转移而来,此时有:

在这里插入图片描述

  • 匹配 2 次,意味着 p [ j − 1 ] p[j - 1] p[j1] p [ j ] p[j] p[j]组成了aa,如果s[i - 1,j] = p[j - 1],则有:
    在这里插入图片描述
  • 匹配3次,意味着 p [ j − 1 ] p[j - 1] p[j1] p [ j ] p[j] p[j]组成了aaa,如果s[i - 2,i - 1,j] = p[j - 1],则有:

在这里插入图片描述

  • 匹配k次,意味着 p [ j − 1 ] p[j - 1] p[j1] p [ j ] p[j] p[j]组成了k个a,如果s[i - k + 1,…,j] = p[j - 1],则有:

在这里插入图片描述

上述过程对应的状态更新过程如下图所示:

在这里插入图片描述
状态转移的优化:总的来看,当 p [ j ] = = ∗ p[j] == * p[j]==时,对于匹配0~k次,我们有
在这里插入图片描述
同时,对于 dp[i-1][j]我们有:

在这里插入图片描述
观察发现,(2)式与(1)式中的后 kk 项刚好相差了一个条件 s[i]=p[j-1],将(2)式代入(1)式可得简化后的「状态转移方程」为:

在这里插入图片描述
p[j]= '*'时,简化后对应的状态更新过程如下图所示:

在这里插入图片描述
总结以上各种情况来看,最终的状态转移方程如下:

在这里插入图片描述
从坐标的角度来看,对应的状态更新过程如下图所示:
在这里插入图片描述

类似题目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值