一.问题描述
Given an input string (s
) and a pattern (p
), implement regular expression matching with support for '.'
and '*'
where:
'.'
Matches any single character.'*'
Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
Example 1:
Input: s = "aa", p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".
Example 2:
Input: s = "aa", p = "a*"
Output: true
Explanation: '*' means zero or more of the preceding element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".
Example 3:
Input: s = "ab", p = ".*"
Output: true
Explanation: ".*" means "zero or more (*) of any character (.)".
Example 4:
Input: s = "aab", p = "c*a*b"
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore, it matches "aab".
Example 5:
Input: s = "mississippi", p = "mis*is*p*."
Output: false
Constraints:
0 <= s.length <= 20
0 <= p.length <= 30
s
contains only lowercase English letters.p
contains only lowercase English letters,'.'
, and'*'
.
二.解题思路
首先先分析问题,可知主要的难点在于处理符号 '.'和'*'的组合,因为我们不知道*要匹配还是不匹配,不同选择可能导致剩下的pattern和string无法匹配。
我们匹配的时候可以迭代字符串来一个个匹配pattern,也可以迭代pattern来一个个匹配字符串,其实都差不多。这里讲解以迭代pattern来匹配字符串为例。假设当前迭代到字符串中的第i个字符和pattern中的第j个pattern。
分析匹配可以总结为以下几种情况:
1.当pattern[j]==str[i] or pattern[j]=='.'的时候:
此时我们需要判断一下,如果pattern[j+1]不等于 '*'的时候,当前匹配的结果等于剩余的string和pattern的匹配结果。
如果当前pattern等于'*'的时候,这时候就可能有两种情况,一种是*与当前的str[i]匹配,一种是*与当前str[i]不匹配(匹配次数为0,直接跳过),因此其结果等于以上两种结果的或。
2.除开情况1,当pattern[j]!=str[i]:
如果pattern[j+1]!='*',那直接没救了,说明匹配失败,返回False, 如果是'*',还可以再抢救一下,看看之后的pattern和剩余的字符串是否匹配。
注意:由于测试样例不干净,有一些无效的pattern,比如连续两个星号的,因此代码中要注意处理这种case。
说完思路我们说说具体的方法,主要两种:
<1>.递归,这种方法根据我上面总结的思路非常简单明了,但是由于递归会有重复计算。不过可以通过记录子递归的结果来节省开销。注意处理当剩余字符串已为空,而剩余pattern是'x*x*x*'或者'*'的结构的情况,这种情况返回也是True.
<2>.动态规划
根据上述的描述,可以知道我们主要的难点在于我们不知道通配符是否要匹配当前的字符,因此需要去尝试 匹配 或者 不匹配的两种结果,然后基于这两个结果来得出我的结论。我们可以换个思路,假设我现在已经有 从0~i-1的string和0~j的pattern中,每个i,j值的string和pattern的匹配结果dp[i][j],比如说第1个字符对第1个pattern的结果dp[1][1],对第1~2个pattern的匹配结果dp[1][2],...,对第1~j个pattern的匹配结果dp[1][j],第1~2个字符对第1个pattern的结果dp[2][1]。。。。。
我们如何计算第0~i+1的子字符串对第0~j的pattern的匹配结果dp[i+1][j]。
分析可知:
1.如果p[j]=s[i]或者p[i]='.'的时候,
dp[i][j]=dp[i-1][j-1]
2.如果p[j]='*',
此时dp[i][j]=dp[i][j-2] (通配符不匹配的情况) or dp[i-1][j] and p[j-2] in {s[i-1], '.'} (通配符匹配的情况)
注意:
要注意把初始的dp[0][0]设置为True,然后对于dp[0]中,要遍历一下pattern,把连续出现 x*x*x*这种结构,dp[0][星号位置]设置为True, 因为假设i为1, pattern='a*b*c*',dp[1][4]=dp[0][2] or 匹配的情况,dp[0][2]对应着空字符串和'a*'的匹配,很明显应该为True对吧,这些细节是实现的时候需要注意的地方,边界问题。
然后返回值应该为dp[n][m], n和m分别为字符串和pattern的长度。
具体看代码吧。
三.源码
# 递归
class Solution:
def isMatch(self, s: str, p: str) -> bool:
def isMatchHelper(s,p,pre):
if not s and not p:return True
if s and not p: return False
if not s and p:
if p[0]=='*':
return isMatchHelper(s,p[1:],p[0])
elif len(p)>1 and p[1]=='*':
return isMatchHelper(s,p[2:],pre)
else:
return False
n=len(p)
if p[0] in {'.', s[0]}:
if n>1 and p[1]=='*':
return isMatchHelper(s,p[2:],p[1]) or isMatchHelper(s[1:],p,pre)
else:
return isMatchHelper(s[1:],p[1:],p[0])
elif p[0]=='*':
# for the case having two asterisk like 'c*a**a'
return False
else:
if n>1 and p[1]=='*':
return isMatchHelper(s,p[2:],p[1])
else:
return False
return isMatchHelper(s,p,None)
# 动态规划
class Solution:
def isMatch(self, s: str, p: str) -> bool:
n,m=len(s),len(p)
dp=[[False]*(m+1) for i in range(n+1)]
dp[0][0]=True # for the start
for k in range(2,m+1,2):
if p[k-1]=='*':dp[0][k]=True
else: break
for i in range(1,n+1):
for j in range(1,m+1):
if p[j-1] in {s[i-1],'.'}:
dp[i][j]=dp[i-1][j-1]
elif p[j-1]=='*':
dp[i][j] = dp[i][j-2] or dp[i-1][j] and p[j-2] in {s[i-1], '.'}
return dp[-1][-1]