算法学习笔记——动态规划:正则表达式匹配

LeetCode 10. 正则表达式匹配
给你一个字符串 s 和一个正则表达式 p( '.'匹配任意字符 , '*'让其前一个字符出现0次/任意次) ,判断两者是否匹配
例如,输入s = "aab" p = "a*b",返回True

思路:
字符串与正则表达式相互匹配的大致过程就是分别用ij指针同步在sp中移动,并比较各个字符是否匹配

为什么需要动态规划呢?
这是因为 '*'通配符带来了一些复杂的情况:一个字符后面有 '*',字符既可以匹配零次/任意多次,但我们不知道匹配多少次是合适的,因此需要把所有可能情况穷举出来,才知道sp是否匹配,这里的重叠子问题就是sp的子串s[0...i]p[0...j]能否相互匹配,由子问题求解sp两个串整体是否匹配

  1. 状态:ij指针的位置
    选择:确定 '*'前的字符要匹配几个字符

  2. dp[i][j]代表s[0...i-1]p[0...j-1]是否匹配

  3. 这里在dp表中增加一行/一列,dp[0][j]代表s为空串,dp[i][0]代表p为空串,原因在于base case:
    p为空串时,s为空串两者才匹配,s非空,必不匹配
    s为空串时,p只要以类似'x*y*'的格式开头,同样能匹配(将其匹配为空串)

  4. 找状态转移方程
    数学归纳思想,假定已知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. 通配符匹配
'?' 可以匹配任何单个字符,'*' 可以匹配任意字符串(包括空字符串)
判断sp是否匹配,如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]
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值