【Leetcode小解析】正则表达式匹配

  1. 正则表达式匹配
    给你一个字符串 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 x0xi1xi,我们的模式串或者匹配串为 p 0 ⋯ p j − 1 p j p_0\cdots p_{j-1}p_j p0pj1pj注意,经过了第一步的处理之后,我们将模式串的每个单位变成了(字符,类型)这样的一个二元组),那么应该如何规划这里的状态?

这里的想法是,任意一个从0位置开始的待匹配串的子串与任意一个从0位置开始的模式串的子串都应该对应一个状态,即需要判定 { x 0 ⋯ x i ′ ∣ i ′ ≤ i } \{x_0\cdots x_{i'}|i'\leq i\} {x0xiii}是否能够被模式串 { p 0 ⋯ p j ′ ∣ j ′ ≤ j } \{p_0\cdots p_{j'}|j'\leq j\} {p0pjjj}识别。

我们设 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=x0xi,ii;Pj=p0pj,jj

假使我们有一个状态矩阵res_matrix,我们想要探究的是res_matrix[i'][j']为True还是False,即 A i ′ A_{i'} Ai能否被 P j ′ P_{j'} Pj识别。

我们以待识别串为aab,模式串为b.*举例,他们构成的状态矩阵为:

image.png

如下图所示的标橘黄的部分,代表着字符串aab能否被模式串b识别,绿色部分表示aa能否被b.*识别。

image.png

理解了状态矩阵的含义之后,我们便可以进行下一步的探索,即如何根据之前的状态计算当前的状态?
如下图所示,假设 X i X_i Xi P j P_j Pj匹配, 为了叙述方便,我们接下来直接使用[i,j]=True表示上述的情况。

1668156660986.png

上图就是我们目前面临的字符串 X i X_i Xi和模式串 P j P_j Pj,我们最容易想到的就是,如果 P j − 1 P_{j-1} Pj1 X i − 1 X_{i-1} Xi1匹配,最后一个模式节点 p j p_j pj和最后一个字符 x i x_i xi匹配,那么自然可以得到[i][j]=True。也即下面这种情况[i-1][j-1]=True, pj = xi

image.png

但是也存在如下这种情况[i][j-1]=True,并且 p j p_j pj是带有*的模式节点,例如a*.*等。

image.png

还有最后一种情况,即[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

image.png

3 初始化的问题

image.png

例如上图,这是我们的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 初始化第一列

image.png

例如上图,第一列的第一处表示字符串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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值