- 正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
1 如何处理模式
对于例如lkak.*jglako*joieha.oihg*
这样的模式串,这里处理为('l', 0), ('k', 0), ('a', 0), ('k', 0), ('.', 2), ('j', 0), ('g', 0), ('l', 0), ('a', 0), ('k', 0), ('o', 2), ('j', 0), ('o', 0), ('i', 0), ('e', 0), ('h', 0), ('a', 0), ('.', 1), ('o', 0), ('i', 0), ('h', 0), ('g', 2)
0、1、2分别代表着字母
、点
、星号
,注意到我们会将*
和其之前的字符合并到一起。
2 如何使用动态规划
所谓动态规划,据我自己的理解,就是找到当下状态与前一个状态之间的关系。
假设我们的待匹配字符串为
x
0
⋯
x
i
−
1
x
i
x_0\cdots x_{i-1}x_i
x0⋯xi−1xi,我们的模式串或者匹配串为
p
0
⋯
p
j
−
1
p
j
p_0\cdots p_{j-1}p_j
p0⋯pj−1pj(注意,经过了第一步的处理之后,我们将模式串的每个单位变成了(字符,类型)
这样的一个二元组),那么应该如何规划这里的状态?
这里的想法是,任意一个从0位置开始的待匹配串的子串与任意一个从0位置开始的模式串的子串都应该对应一个状态,即需要判定 { x 0 ⋯ x i ′ ∣ i ′ ≤ i } \{x_0\cdots x_{i'}|i'\leq i\} {x0⋯xi′∣i′≤i}是否能够被模式串 { p 0 ⋯ p j ′ ∣ j ′ ≤ j } \{p_0\cdots p_{j'}|j'\leq j\} {p0⋯pj′∣j′≤j}识别。
我们设 A i ′ = x 0 ⋯ x i ′ , i ′ ≤ i ; P j ′ = p 0 ⋯ p j ′ , j ′ ≤ j A_{i'}=x_0\cdots x_{i'},i'\leq i;P_{j'}=p_0\cdots p_{j'},j'\leq j Ai′=x0⋯xi′,i′≤i;Pj′=p0⋯pj′,j′≤j。
假使我们有一个状态矩阵res_matrix
,我们想要探究的是res_matrix[i'][j']
为True还是False,即
A
i
′
A_{i'}
Ai′能否被
P
j
′
P_{j'}
Pj′识别。
我们以待识别串为aab
,模式串为b.*
举例,他们构成的状态矩阵为:
如下图所示的标橘黄的部分,代表着字符串aab
能否被模式串b
识别,绿色部分表示aa
能否被b.*
识别。
理解了状态矩阵的含义之后,我们便可以进行下一步的探索,即如何根据之前的状态计算当前的状态?
如下图所示,假设
X
i
X_i
Xi与
P
j
P_j
Pj匹配, 为了叙述方便,我们接下来直接使用[i,j]=True
表示上述的情况。
上图就是我们目前面临的字符串
X
i
X_i
Xi和模式串
P
j
P_j
Pj,我们最容易想到的就是,如果
P
j
−
1
P_{j-1}
Pj−1和
X
i
−
1
X_{i-1}
Xi−1匹配,最后一个模式节点
p
j
p_j
pj和最后一个字符
x
i
x_i
xi匹配,那么自然可以得到[i][j]=True
。也即下面这种情况[i-1][j-1]=True, pj = xi
:
但是也存在如下这种情况[i][j-1]=True
,并且
p
j
p_j
pj是带有*
的模式节点,例如a*
、.*
等。
还有最后一种情况,即[i-1][j]=True
,同时为了把
x
i
x_i
xi这个字符也给匹配上,那么
p
j
p_j
pj必须是带
∗
*
∗的节点,并且
p
j
p_j
pj对应的字符应该与
x
i
x_i
xi相等,即使pj = xi
3 初始化的问题
例如上图,这是我们的res_matrix
矩阵,前两节说的[i][j]
的True或者False就放在这个里面,比如当我们想要计算res_matrix[2][1]
也就是图中橙色对应的位置时,根据第二节的分析,我们需要来自res_matrix[2-1][1-1],res_matrix[2-1][1],res_matrix[2][1-1]
这些位置上的信息,以此类推,我们需要的第一推动力,也就是初始化的位置,就是矩阵的第一行和第一列。有了第一行和第一列的状态,我们就可以计算剩下的任意位置的True或者False。
为了表示方便,我们对 p j p_j pj进一步进行细化, p j = ( c h a , t y p e ) p_j=(cha,type) pj=(cha,type), c h a cha cha表示字符, t y p e type type表示是否为‘*’、‘.’或者字母。
3.0 初始化[0,0]位置
即计算 r e s m a t r i x [ 0 ] [ 0 ] = { x 0 = = p j . c h a p j . t y p e = 字 母 o r p j . t y p e = = ′ ∗ ′ T r u e p j . t y p e = ′ d o t ′ resmatrix[0][0]=\begin{cases}x_0==p_j.cha&p_j.type=字母\quad or\quad p_j.type=='*'\\True& p_j.type='dot'\end{cases} resmatrix[0][0]={x0==pj.chaTruepj.type=字母orpj.type==′∗′pj.type=′dot′
3.1 初始化第一列
例如上图,第一列的第一处表示字符串a
能否与模式串b
进行匹配,第一列的第二处表示字符串aa
能否与模式串b
进行匹配,第三处表示模式串aab
能否与b
进行匹配。可以看出,对第一列的初始化本质是检验模式串的第一个模式节点
p
0
p_0
p0能否匹配
X
0
,
X
1
⋯
,
X
i
X_0,X_1\cdots,X_i
X0,X1⋯,Xi这些子串(不是字符)。很显然这里要求
p
0
.
t
y
p
e
=
′
∗
′
p_0.type='*'
p0.type=′∗′,也就是
p
0
p_0
p0能匹配多个字符。
如果要判断resmatrix[i][0]=True,首先要保证 p 0 . t y p e = ′ ∗ ′ & x i = p 0 . c h a p_0.type='*' \quad \& \quad x_i=p_0.cha p0.type=′∗′&xi=p0.cha,同时要保证resmatrix[i-1][0]也应该为True。
3.2 初始化第一行
初始化第一行,就是要判断 P 0 , P 1 , ⋯ , P n P_0,P_1,\cdots, P_n P0,P1,⋯,Pn这些子模式串能否匹配字符串的第一个字符 x 0 x_0 x0。
简单来说,在没有匹配到之前,如果遇到‘*’,都是False,如果碰到非 x 0 x_0 x0的字符,直接停止判断,因为接下来的子模式串都含有这个字符,都不可能再和 x 0 x_0 x0匹配。如果匹配到一个‘.’或者匹配到和 x 0 x_0 x0相同的字符之后,设置为True,并且用某种方式记录下来,说明这个字符已经被匹配过了。如果紧接着碰到‘*’仍然为True,但是接下来如果再次碰到任何一个字母或者‘.’,则都是False并且直接停止判断,因为模式串已经和 x 0 x_0 x0匹配过了。
这里需要注意的一点是,要先独立地判断一下如果resmatrix[0][0]=False时,到底是因为什么原因没有匹配上,如果是因为 p 0 . c h a ≠ x 0 & p 0 . t y p e = 字 母 p_0.cha \neq x_0 \quad \& \quad p_0.type=字母 p0.cha=x0&p0.type=字母,那接下来就不用判断了,因为模式串的第一个节点就和 x 0 x_0 x0匹配不上。
4 代码
class Solution(object):
t_letter = 0
t_dot = 1
t_star = 2
def isMatch(self, s, p):
# start = time.time()
stack = []
for c in p:
if c == '.':
stack.append((c, self.t_dot))
elif c == '*':
last_node = stack.pop()
stack.append((last_node[0], self.t_star))
else:
stack.append((c, self.t_letter))
# end = time.time()
# print(end - start)
# res = isMatchNorm(s, 0, stack, 0)
res = self.isMatch2(s, stack)
# eend = time.time()
# print(eend - end)
return res
def isMatch2(self, s, stack):
res_matrix = [[False for _ in range(len(stack))] for _ in range(len(s))]
res_matrix[0][0] = self.is_same(s[0], stack[0]) # 初始化[0,0]
c = stack[0][0]
if stack[0][1] == self.t_star:
for i in range(1, len(s)):
if (s[i] == c and res_matrix[i - 1][0]) or c == '.':
res_matrix[i][0] = True
count_c = 1 if res_matrix[0][0] and (not stack[0][1] == self.t_star) else 0 # count_c记录是否已经匹配过x0
if not(not res_matrix[0][0] and stack[0][1] == self.t_letter):# 如果满足[0][0]==False并且模式串的第一个节点的类型还是字符,则不初始化第一行了。反之则初始化
for i in range(1, len(stack)):
if stack[i][1] == self.t_letter or stack[i][1] == self.t_dot:
if count_c == 1:
break
if not stack[i][0] == s[0] and stack[i][1] == self.t_letter:
break
res_matrix[0][i] = True
count_c = 1
else:
res_matrix[0][i] = count_c == 1 or self.is_same(s[0], stack[i])
for i in range(1, len(s)):
for j in range(1, len(stack)):
res_matrix[i][j] = self.is_same(s[i], stack[j]) \
and (((stack[j][1] == self.t_star) and res_matrix[i - 1][j]) or res_matrix[i - 1][j - 1])
res_matrix[i][j] = res_matrix[i][j] or (stack[j][1] == self.t_star and res_matrix[i][j - 1])
return res_matrix[len(s) - 1][len(stack) - 1]
def is_same(self, s, node):
if s == node[0] or node[0] == '.':
return True
return False