剑指offer JZ52 正则表达式匹配 Python 多解

一.题目描述

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

二.解题思路

<1>递归

首先分析一下匹配可能的集中情况。

1.s和pattern都为空:返回True

2.s不为空pattern为空:返回False

3.s为空,pattern不为空:这个可能匹配也可能不匹配,比如'a*b*c*'这种pattern是可以匹配空字符串的(不知道有没有这么丧心病狂的测试用例)。这种情况只需要判断pattern里面是不是每隔一位出现一个*就行了。

讨论完特殊情况,再来讨论常规情况:

假设当前匹配的位置为s[i]和p[k],

我们将s和pattern分为pattern[k+1]为‘*’和不为'*'的情况:

若pattern[k+1]!='*',这就很简单,判断s[i]和p[k]是否相等,或者p[k]='.',如果相等,我们递归匹配match(s[i+1:end],pattern[k+1:end]),

若不符合,则不匹配。

若pattern[k+1]='*',又要细分为三种情况:

1.如果s[i]!=p[k],说明'*'在这里的作用的匹配前面的字符数次数为0,递归匹配match(s[i:end],p[k+2:end])

2.如果s[i]==p[k],这里'*'的作用可以为匹配前面的字符次数为0,也可以为匹配前面的字符数为1.

比如'aa','a*aa',此样例中'*'作用为匹配前面次数为0,pattern向后移动2位.

比如'aaa','a*aa',此样例中'*'作用为匹配前面次数为1,pattern可以向后移动2位,也可以不向后移动,但是在实现中,我们将将匹配次数为0和1结合起来了,匹配多次就是n个匹配1次加上一个匹配0次结束,因此实现中这里pattern不向后移动即可,减少递归重复次数。

综上,我们需要递归此两种情况,return match(s[i:end],pattern[k+2:end] or match(s[i+1:end],pattern[k:end]

<2>动态规划

和递归的思路类似,递归是自上而下的处理方式,而动态规划是自下而上的处理方式。

对于动态规划来说,有正序即0~n的,也有n~0逆序的。观察递归的处理,都是要看后面几个的情况,因此这道题比较适合逆序的(当然正向的也可以做)。

分析也可知,若从后面开始处理,假设目前处理s的后i为和pattern的后k位,不管s的i前面的字符和pattern的k前面的字符是啥,都不会影响i之后和k之后是否匹配的结果。

我们用dp[i][j]来表示s后i个字符与pattern后j个字符的匹配情况。

简单来说就是我们从s的最后一位开始,判断s的后i位与pattern的后k位(k=0~pattern.length)是否匹配,然后对于当前后i+1位来说,其基于后i位的状态来判断。

dp的大小为s.length行,pattern.length列。

参考牛客网:

链接:https://www.nowcoder.com/questionTerminal/45327ae22b7b413ea21df13ee7d6429c?f=discussion
来源:牛客网

 * 有了前面的认识,我们考虑用动态规划解题,动态规划有正向的和反向的,到底怎么取呢?
     * 看下前面的递归调用:match1(str, i + 1, pattern, j + 1)相当于 dp[i][j]=dp[i+1][j+1]
     * 适合反向遍历,于是,我们可以初始化boolean dp[len1+1][len2+1] 其中len1=str.length,len2=pattern.length
     * 初始化dp[len1][len2]=true,含义是:str=aaa 和pattern=aa* 从末尾开始匹配 "" 和 "" 一定为true
     * 这个时候开始循环
     * 1.外循环:因为我们要用aa*匹配aaa,以aaa为外循环,注意,从""开始匹配接下来a,aa,aaa
     * for(int i = len1;i>=0;i--)   
     * 2.内循环:拿aa*取匹配:匹配顺序 "*" "a*" "aa*",于是
     * for(int j = len2 - 1;j>=0;j--)
     * 循环体内部逻辑,参考递归调用:
* =============  *版本1:=============
   
     * 先看下一个是否是“*”,再看当前是否相等
     * 1.若下一个是"*",分为当前相等和当前不等
     *      1.1:当前相等dp[i][j]=dp[i][j+2] || dp[i+1][j]
     *      1.2:当前不等dp[i][j]=dp[i][j+2] 
     * 2.若不是"*",但是当前相等 d[i][j]= dp[i + 1][j + 1];

主要说的一点就是我们外循环得从i=s.length开始,相当于从空字符串开始匹配,因为即使是空字符串,像'a*'这样的pattern也是能与之匹配的,所以要考虑到这种情况。

动态规划的时间复杂度是O(MN).

递归时间复杂度是指数级别,当然你可以优化,不过这样就类似动态规划了。

<3>有限/无限状态机

巨佬做法。请自行查找相关资料

三.源码

# 递归
class Solution:
    # s, pattern都是字符串
    def match(self, s, pattern):
        # write code here
        if not s and not pattern:return True
        if s and not pattern:return False
        if not s and pattern:
            if len(pattern)>1 and pattern[1]=='*':
                return self.match(s, pattern[2:])
            else:return False
        if len(pattern)>1 and pattern[1]=='*':
            if pattern[0]==s[0] or pattern[0]=='.':
                return self.match(s,pattern[2:]) or self.match(s[1:],pattern)
            else:
                return self.match(s,pattern[2:])
        else:
            if pattern[0]==s[0] or pattern[0]=='.':
                return self.match(s[1:],pattern[1:])
            else:return False

# 动态规划
class Solution:
    # s, pattern都是字符串
    def match(self, s, pattern):
        # write code here
        if not s and not pattern:return True
        if s and not pattern:return False
        n,m=len(s),len(pattern)
        dp=[[0]*(m+1) for i in range(n+1)]
        dp[n][m]=1
        for i in range(len(s),-1,-1):
            for j in range(len(pattern)-1,-1,-1):
                if j+1<m and pattern[j+1]=='*':
                    if i!=n and (s[i]==pattern[j] or pattern[j]=='.'):
                        dp[i][j]=dp[i][j+2] | dp[i+1][j]
                    else:
                        dp[i][j]=dp[i][j+2]
                else:
                    if i!=n and (pattern[j]==s[i] or pattern[j]=='.'):
                        dp[i][j]=dp[i+1][j+1]
        return dp[0][0]

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值