一.题目描述
请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含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]