LeetCode10:正则表达式匹配(python版)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/vivian_ll/article/details/87936420

题目描述:请实现一个函数用来匹配包括’.’和’*’的正则表达式。模式中的字符’.’表示任意一个字符,而’*’表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”ab*ac*a”匹配,但是与”aa.a”和”ab*a”均不匹配

思路1——递归:本题就是很直观的字符串的匹配,难点在于当遇到模式中两个特殊字符怎么处理和对于各种形式字符串的全面考虑。这里采用递归的方式,每次只匹配s中一个字符(模式串pattern, 被匹配字符串s):
。处理判断过程如下:

  • 如果 s和pattern都为空,匹配成功
  • 如果pattern是空串,而s不是,匹配失败
  • 如果s,pattern均不是空串(长度至少为1),考虑到pattern中‘ * ’前字符可以出现0次,所以不能简单比较s和pattern的第一个字符是否相等,这里分为两种情况考虑:
    • 如果pattern的第二个字符是‘ * ’
      • 如果s与pattern的第一个字符匹配(即s与pattern的第一个字符相等或者pattern第一个字符为‘ . ’),剩余部分有两种匹配方式:(1). s后移一位,相当于认为‘ * ’前的字符在s中出现不止一次,(2).pattern后移两位,相当于认为‘ * ’前的字符在s中只出现一次。
      • 否则模式串pattern后移两位,相当于认为‘ * ’前的字符在s中出现了0次;
    • 如果pattern的第二个字符不是‘ * ’:如果s与pattern的第一个字符匹配(含义同上),s和pattern同时后移一位,继续匹配;否则匹配失败

常规写法 :

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        # 递归写法
        # s已被匹配且p已耗完
        if not s and not p:  # not比len()==0和s==[]或s==""更简便
            return True
        # p已耗完但s未被完全匹配
        if len(s) > 0 and len(p) == 0:  # 或if not p
            return False

        # 如果模式第二个字符是*
        if len(p) > 1 and p[1] == '*':
            if len(s) > 0 and (s[0] == p[0] or p[0] == '.'):
                # 如果第一个字符匹配,三种可能1、模式后移两位;2、字符串移1位
                return self.isMatch(s, p[2:]) or self.isMatch(s[1:], p)
            else:
                # 如果第一个字符不匹配,模式往后移2位,相当于忽略x*
                return self.isMatch(s, p[2:])
        # 如果模式第二个字符不是*
        if len(s) > 0 and (s[0] == p[0] or p[0] == '.'):
            return self.isMatch(s[1:], p[1:])
        else:
            return False

简化写法:

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        # 递归简化写法
        if p == "":
            return s == ""
        if len(p) > 1 and p[1] == "*":
            return self.isMatch(s, p[2:]) or (s and (s[0] == p[0] or p[0] == '.') and self.isMatch(s[1:], p))
        else:
            return s and (s[0] == p[0] or p[0] == '.') and self.isMatch(s[1:], p[1:])

思路2——动态规划

动态规划的思路是找到一个递推公式,由前向后或者由后向前求解题目。

在字符串匹配问题中,基本思想是:从前往后,维护两个指针(一个指针遍历s,一个指针遍历p,并不断判断当前两指针的子串是否匹配),这种思想可以用一个二维布尔表dp来实现。

 二维表格dp的大小为(len(s)+1)*(len(p)+1)dp[i][j]表示,若s的子串[0,i)和p的子串[0,j)匹配,dp[i][j]=True ,若不匹配dp[i][j]=False。最后返回的就是右下角位置的数字。

2.1 首先完善dp表的第一行和第一列。

      当 p 为空串时,只有目标串 s 的空串才能与 p 匹配;

      当 s 为空串时,只有 p 为 空串 或者 为 x*y* 的形式才能与 s 匹配。


#2.1 初始化dp表,初始化表的第一列和第一行
dp = [[False for j in range(len(p)+1)] for i in range(len(s)+1)]    #  初始化dp表
dp[0][0] = True                                                     #  s和p 都为空时匹配
#  s 为空串时
for j in range(1, len(p)+1):                                        #  [j-1]为p的真实索引
    dp[0][j] = (j>=2) and (p[j-1]=="*") and dp[0][j-2]              #   只有x*能匹配空串,若有*,它的真值一定和dp[0][j-2]的相同
 
#  注意:and的语句可以转换成if判断语句,如: dp[0][j] = (j>=2) and (p[j-1]=="*") and dp[0][j-2],可转化为如下if语句:
     #if p[i-1]=='*':
         #if i>=2:
             #dp[0][i]=dp[0][i-2]

2.2 然后两层循环填充剩下的部分。

      每次s字符串往下走一个字符,和所有的p子串进行匹配,接下来分两种情况进行分类。

      ( a )  假设当前位置为dp[i][j],若p[j-1] == "*"时,"*"的用法分为两种(1:代表空串  2:代表一个或者多个前一个字符)

要想dp[i][j] =1,需要满足下列条件中的任一个:

                    (a.1)dp[i][j-2] =1时,此时"*"代表空串

                    (a.2)dp[i-1][j] =1时满足(p[j-2]==s[i-1] or p[j-2]=="."),此时"*"代表对前一字符的复制

      ( b )  若p[j-1]!= "*"时,要想dp[i][j] =1,需满足(p[j-1]==s[i-1] or p[j-1]=="."),还要判断前面的是否匹配,即dp[i-1][j-1]的值是否为True。

3.  图解例子:

假设:  s="aaaa"   p="a*b*"  求两者是否匹配,做下图所示:

红色的箭头: p[j-1] != "*"时,

绿色的箭头:p[j-1] == "*"时, "*" 代表空串

黄色的箭头:p[j-1] == "*"时, "*" 代表一个或者多个前面的元素

dp = [[False for j in range(len(p) + 1)] for i in range(len(s) + 1)]  # 初始化二维表dp
        print(dp)
        dp[0][0] = True  # s 和 p 都为空时
        #  若 s 为空时
        for j in range(1, len(p) + 1):
            # dp[0][j]= (p[j-1]=="*")and(j>=2)and(dp[0][j-2])            #  等同于下列语句
            if p[j - 1] == '*':
                if j >= 2:
                    dp[0][j] = dp[0][j - 2]
        print(dp)

        for i in range(1, len(s) + 1):
            for j in range(1, len(p) + 1):
                #  j-1才为正常字符串中的索引
                #  p当前位置为"*"时,(代表空串--dp[i][j-2]、一个或者多个前一个字符--( dp[i-1][j] and (p[j-2]==s[i-1] or p[j-2]=='.'))
                if p[j - 1] == '*':
                    dp[i][j] = dp[i][j - 2] or (
                                dp[i - 1][j] and (p[j - 2] == s[i - 1] or p[j - 2] == '.'))  # dp[i][j-1] or
                #  p当前位置为"."时或者与s相同时,传递dp[i-1][j-1]的真值
                else:
                    dp[i][j] = (p[j - 1] == '.' or p[j - 1] == s[i - 1]) and dp[i - 1][j - 1]

        return dp[len(s)][len(p)]

思路3——正则匹配
一句话!

import re
class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        # 正则匹配
        return re.match('^' + p + '$', s) != None

re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。
^匹配字符串的开头 $匹配字符串的末尾 +匹配1个或多个的表达式。
参考网址:
https://blog.csdn.net/qq_20141867/article/details/80910724
https://blog.csdn.net/chenhua1125/article/details/80471839
https://blog.csdn.net/weixin_39781462/article/details/82999610
https://cloud.tencent.com/developer/article/1092371
https://hk029.gitbooks.io/leetbook/动态规划/010. Regular Expression Matching/010. Regular Expression Matching.html

猜你喜欢

转载自blog.csdn.net/vivian_ll/article/details/87936420