[10][困难][动态规划] 正则表达式匹配

题目描述

10. 正则表达式匹配 剑指 Offer 19. 正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

'.' 匹配任意单个字符 '*' 匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

说明:

s 可能为空,且只包含从 a-z 的小写字母。 p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。 示例 1:

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:

输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。

示例 4:

输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。

示例 5:

输入:
s = "mississippi"
p = "mis*is*p*."
输出: false

解题思路

动态规划

状态的定义比较直观, dp[i][j]表示字符串s的前i个字符能否与模式串p的前j个字符匹配.

状态转移方程有些麻烦. 可以看出*字符是特殊的字符, 需要以p[j] == '*'与否, 分别讨论.

p[j]不为*

如果p[j] == '.', 那么可以与任何一个字符匹配, 因此有dp[i][j] = dp[i - 1][j - 1].

如果p[j]为任一字母, 需要判断p[j], s[i]是否相等, 如果相等, 则dp[i][j] = dp[i - 1][j - 1]; 如果不相等, 则dp[i][j] = False.

p[k]为*

由于*代表的是匹配0次或任意多次, 因此, 分为两种情况讨论, 只要满足其中的任何一种情况, 即为True.

匹配0次的情况, 即跳过当前的*字符, 以及前面紧邻的.或字母字符. 则有dp[i][j] = dp[i][j - 2]

匹配1~n次的情况, 需要判断p[j-1]s[i], s[i-1], ...的字符是否相等, 一直找到不相等的字符为止, 例如s[i-a]p[j-1]不相等, 则dp[i][j] = dp[i-a][j-2].

但其实我们只需要比较p[j-1]s[i]是否相等, 因此我们是从左到右搜索, 这样p[j-1]s[i]之前的字符是否相等, 已经包含在dp[i-1][j]中, 而无需向前继续寻找.

因此我们有, 如果p[j-1]s[i]相等, 则dp[i][j] = dp[i - 1][j], 否则为False.

因此p[k]*的情况下, dp[i][j] = dp[i][j - 2] or (s[i] == p[j - 1] and dp[i - 1][j])

状态转移方程考虑完毕.

最后考虑下初始状态. 要考虑空字符串和模式串的情况, 因此如果字符串和匹配串的长度分别为nm, 那么我们状态矩阵的大小应为(n + 1, m + 1), 各个维度的索引0代表空字符串.

dp[0][0] = True, 空字符串肯定是匹配的. dp[i][0]肯定是False, 因为任何非空字符串肯定与空模式串不匹配. dp[0][j]是要注意的情况, 我们对于非*位置肯定是False, 而*位置, 只能考虑不匹配任何字符的情况, 因此有dp[0][j] = dp[0][j - 2]. 这样其实遇到第一个连续的非*字符, 至此开始, 后续都是False.

整体代码如下:

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        n, m = len(s), len(p)
        dp = [[False] * (m + 1) for _ in range(n + 1)]

        def match(sc, pc):
            return True if pc == '.' else sc == pc

        # 初始化
        dp[0][0] = True
        for j in range(1, m + 1):
            if p[j - 1] == '*':
                dp[0][j] = dp[0][j - 2]

        for i in range(1, n + 1):
            for j in range(1, m + 1):
                if p[j - 1] == '*':
                    dp[i][j] = dp[i][j - 2] or (match(s[i - 1], p[j - 2]) and dp[i - 1][j])
                elif match(s[i - 1], p[j - 1]):
                    dp[i][j] = dp[i - 1][j - 1]
        print(dp)
        return dp[-1][-1]

最后更新于