LeetCode 10. 正则表达式匹配
给你一个字符串 s 和一个正则表达式 p('.'
匹配任意字符 ,'*'
让其前一个字符出现0次/任意次) ,判断两者是否匹配
例如,输入s = "aab" p = "a*b"
,返回True
思路:
字符串与正则表达式相互匹配的大致过程就是分别用i
和j
指针同步在s
和p
中移动,并比较各个字符是否匹配
为什么需要动态规划呢?
这是因为 '*'
通配符带来了一些复杂的情况:一个字符后面有 '*'
,字符既可以匹配零次/任意多次,但我们不知道匹配多少次是合适的,因此需要把所有可能情况穷举出来,才知道s
和p
是否匹配,这里的重叠子问题就是s
和p
的子串s[0...i]
和p[0...j]
能否相互匹配,由子问题求解s
和p
两个串整体是否匹配
-
状态:
i
和j
指针的位置
选择:确定'*'
前的字符要匹配几个字符 -
dp[i][j]
代表s[0...i-1]
与p[0...j-1]
是否匹配 -
这里在dp表中增加一行/一列,
dp[0][j]
代表s
为空串,dp[i][0]
代表p
为空串,原因在于base case:
p
为空串时,s
为空串两者才匹配,s
非空,必不匹配
s
为空串时,p
只要以类似'x*y*'
的格式开头,同样能匹配(将其匹配为空串) -
找状态转移方程
数学归纳思想,假定已知dp[i-1][j-1]
,怎么求dp[i][j]
注意,这里dp[i][j]
对应的是字符s[i-1]
和p[j-1]
- 转移方程:
- 若
s[i-1]
与p[j-1]
匹配(p[j-1]=='.'
也算匹配),则s[0...i-1]
与p[0...j-1]
是否匹配取决于s[0...i-2]
与p[0...j-2]
是否匹配 - 若
s[i-1]
与p[j-1]
不匹配,也要考虑p[j-1]
为'*'
的特殊情况:
①p[j-1] =='*'
且其前一个字符能匹配s[i-1]
,则应选择匹配0次/多次
匹配0次:看p
前面(除去'x*'
)的部分与当前s
是否匹配
匹配多次:看s
前面(除去s的当前字符)的部分与当前p
是否匹配
②p[j-1] =='*'
但其前一个字符不能匹配s[i-1]
,唯一的选择是'x*'
作为空串匹配0次
③p[j-1] !='*'
,s和p尾部是不同的普通字符,必不能匹配
class Solution:
def isMatch(self, s: str, p: str) -> bool:
match = lambda s_i, p_j: p_j == s_i or p_j == '.'
s_len = len(s)
p_len = len(p)
# dp[i][j]代表s[0...i-1]与p[0...j-1]是否匹配,dp[0][j]代表s为空串,dp[i][0]代表p为空串
dp = [[False for _ in range(p_len + 1)] for _ in range(s_len + 1)]
############# 注意,这里dp[i][j]对应的是字符s[i-1]和p[j-1] ##################
# base case
# p为空串时,s为空串两者才匹配,s非空必不匹配
dp[0][0] = True
# s为空串时,p只要以类似'x*y*'的格式开头,同样能匹配(将其匹配为空串)
for j in range(2, p_len + 1):
if p[j - 1] == '*': # 当前的*与上一个字符能匹配空串,转而看更前面的部分能否匹配空串
dp[0][j] = dp[0][j - 2]
for i in range(1, s_len + 1):
for j in range(1, p_len + 1):
# 匹配,看之前部分是否也匹配
if match(s[i - 1], p[j - 1]):
dp[i][j] = dp[i - 1][j - 1]
# 字符不同,但要考虑'*'通配符的情况
elif p[j - 1] == '*':
# 如果'*'的前一个字符能匹配s中的字符
if match(s[i - 1], p[j - 2]):
# 可能匹配0次/多次
# 匹配0次,则看p前面(除去'x*')的部分是否匹配
# 匹配多次,则看s前面(除去s的当前字符)的部分是否匹配
dp[i][j] = dp[i][j - 2] or dp[i - 1][j]
# '*'的前一个字符与s中的字符不匹配,试将不匹配的字符作为空串,匹配0次
else:
dp[i][j] = dp[i][j - 2]
# s和p尾部是不同的普通字符,必不能匹配
else:
dp[i][j] = False
return dp[s_len][p_len]
练习题
LeetCode 44. 通配符匹配
'?'
可以匹配任何单个字符,'*'
可以匹配任意字符串(包括空字符串)
判断s
和p
是否匹配,如s = "adceb"p = “*a?*b”,返回True
class Solution:
def isMatch(self, s: str, p: str) -> bool:
match = lambda s_i, p_j: p_j == s_i or p_j == '?'
s_len = len(s)
p_len = len(p)
# dp[i][j]代表s[0...i-1]与p[0...j-1]是否匹配,dp[0][j]代表s为空串,dp[i][0]代表p为空串
dp = [[False for _ in range(p_len + 1)] for _ in range(s_len + 1)]
############# 注意,这里dp[i][j]对应的是字符s[i-1]和p[j-1] ##################
# base case
dp[0][0] = True
for j in range(1, p_len + 1):
if p[j - 1] == '*': # 能匹配为空串,看前面是否也匹配
dp[0][j] = dp[0][j - 1]
for i in range(1, s_len + 1):
for j in range(1, p_len + 1):
if match(s[i - 1], p[j - 1]):
# 匹配,看之前是否也匹配
dp[i][j] = dp[i - 1][j - 1]
elif p[j - 1] == '*':
# 不匹配,但注意*通配符的特殊情况
# 可选择匹配0次/多次
dp[i][j] = dp[i][j - 1] or dp[i - 1][j]
else:
# 不匹配
dp[i][j] = False
return dp[s_len][p_len]