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

二、注意事项

  • ”.*“可匹配任意长度字符串
  • 连续偶数个”*“字符代表0个前面的字符,连续奇数个“*”与1个“*”相同。

三、思路

1. 递归

拿到题目首先想到的办法,大概的思路如图,递归计算方向是从右向左,主要是各种情况的考虑。(图中s-1=s.substr(1))
(s的后缀子串包括空字符串)

在这里插入图片描述
s=”aedcbbbe“ , p=“a.*a**b*..” 的匹配过程

在这里插入图片描述

2. 动态规划

看了题解之后发现用动态规划方法,从左往右逐步计算更加科学,精简表达式还可以很好合并一些特殊情况。

  • 当p[j]!=*的时候,只需匹配当前两个字符,成功则为1,不成功则为0。

i f ( p [ j ] ! = ∗ ) d [ i ] [ j ] = { m a t c h ( s [ i ] , p [ j ] ) d [ i − 1 ] [ j − 1 ] p [ j ] ! = s [ i ] 0 if(p[j]!=*) \quad d[i][j]=\begin{cases}match(s[i],p[j])&d[i-1][j-1]\\p[j]!=s[i]& 0\\\end{cases} if(p[j]!=)d[i][j]={match(s[i],p[j])p[j]!=s[i]d[i1][j1]0

  • 当p[j]=*的时候,则需判断p[j-1],如果s[i]=p[j-1],又因为*代表0或多个,则取决于d[i-1][j]和d[i][j-2],只要其中一个匹配则成功。如果不相等,则取决于d[i][j-2](这里直接包括了连续*的情况)。

i f ( p [ j ] = ∗ ) d [ i ] [ j ] = { m a t c h ( s [ i ] , p [ j ] ) d [ i − 1 ] [ j ] ∥ d [ i ] [ j − 2 ] s [ i ] ! = p [ j − 1 ] d [ i ] [ j − 2 ] if(p[j]=*) \quad d[i][j]=\begin{cases}match(s[i],p[j])& d[i-1][j] \Vert d[i][j-2]\\s[i]!=p[j-1]& d[i][j-2]\end{cases} if(p[j]=)d[i][j]={match(s[i],p[j])s[i]!=p[j1]d[i1][j]d[i][j2]d[i][j2]
m a t c h ( s [ i ] , p [ j ] ) = { s [ i ] = p [ j ] 1 p [ j ] = . 1 e l s e 0 match(s[i],p[j])=\begin{cases}s[i]=p[j]& 1\\p[j]=.& 1\\else& 0\end{cases} match(s[i],p[j])=s[i]=p[j]p[j]=.else110

四、代码

1.递归方法代码如下

class Solution {
public:
    bool match(string s,string p){
        if(p==""){
            if(s=="") return true;
            else return false;
        }else{
            if(p.size()>1&&p[1]=='*'){
                int i,j;
                for(i=3,j=2;i<p.size()&&j<p.size();){
                    if(p[i]=='*'&&p[i-1]==p[0]) return match(s,p.substr(i-1));
                    //如果有相同的对应字符,将其合并如"a*a*a*a*a*a*a*a*",合并为"a*"
                    else if(p[j]=='*') j++;
                    //每两个连续的"*"抵消一个字符串,如a**代表0个字符,a***=a*
                    else break;
                }
                if(j%2) return match(s,p.substr(j));
                //跳连续的**抵消的匹配字符
                if(p[0]=='.'){ 
                    if(p.size()==2) return true;
                    //如果匹配字符为.*,则所有字符都匹配
                	else {
                        for(i=0;i<s.size()+1;i++){
                		    if(match(s.substr(s.size()-i),p.substr(j))) return true;
					    }
                        return false;
                        //对于.*后面还有字符串的,则在s的后缀字符串中找到一个匹配成功即整个匹配成功,否则匹配失败。
                    }
                }else {
                    if(s.size()>0&&s[0]==p[0]&&match(s.substr(1),p)) return true;
                    //如果当前s与p第一个字符匹配,则存在两种情况,一种s[0]匹配了一个*(即*未被抵消),一种不匹配(即*当成0被抵消了),只要一种情况为true,匹配就成功。
                    else return match(s,p.substr(j));
                    //当前s与p第一个字符不匹配,则肯定不会匹配*(*直接当成0被抵消),则跳过继续递归。
                }
            }else if(s.size()>0&&(p[0]=='.'||s[0]==p[0])) return match(s.substr(1),p.substr(1));
            //当前没有*匹配的时候,直接将s,p对应字符匹配,成功则继续递归匹配,否则匹配失败。
            else return false;     
        }
    }
    bool isMatch(string s, string p) {
        return match(s,p);
    }
};

递归写法相对不那么清晰,各种情况交叉在一起。

2.动态规划方法代码如下

class Solution {
public:
    bool match(char s,char p){
        if(p!=' '&&s!=' '&&(s==p||p=='.')) return true;
        else return false;
    }
    bool isMatch(string s, string p) {
        bool d[s.size()+1][p.size()+1];
        int i,j,k;
        char a,b;
        d[0][0]=true;
        //两个空字符串匹配不成功
        for(i=1;i<s.size()+1;i++){
            d[i][0]=false;
            //当p为空而s不为空时,一定匹配不成功
        }
        for(i=0;i<s.size()+1;i++){
            for(j=1;j<p.size()+1;j++){
                a=i>0?s[i-1]:' ';
                //如果s为空则相当于match不匹配的情况。
                b=j>1?p[j-2]:' ';
                //如果p长度为一,考虑单独'*'的情况,相当于d[i][0]
                if(p[j-1]!='*'){
                    if(match(a,p[j-1])) d[i][j]=d[i-1][j-1];
                    else d[i][j]=false;
                }else{
                    k=j>2?2:j;
                    if(match(a,b)) d[i][j]=d[i-1][j]||d[i][j-k];
                    else d[i][j]=d[i][j-k];
                }
            }
        }
        return d[s.size()][p.size()];
    }
};

用动态规划时空复杂度都减小了很多。


总结

  • 典型的动态规划思路
  • 正则表达式的理解
  • 各种特殊情况的考虑,在写代码的时候要考虑边界情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值