每天一道算法题(2020.06.04)-正则匹配

题目描述

给你一个字符串 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

解题思路

动态规划求解,考虑两大要素:状态,转移方程。

状态
i 表示字符串s的长度,j表示字符规律 p的长度,那么有:
dp[i][j]表示某一长度为i的s与某一长度为j的字符规律p是否匹配。

转移方程
接下来需要考虑dp[i][j]有哪些呢?也就是说,可以由哪些子问题转移而来呢?这里相对来说情况比较多样,主要原因在于字符规则p可以取值为字符,以及 ‘.’ 和 ‘*’ 。

  • 首先考虑最简单的情况,即p[j] = s[i] = 字母,那么 dp[i-1][j-1]的匹配情况与 dp[i][j]的一致,这个时候转移方程为:
    dp[i][j] = dp[i-1][j-1]
    举个例子:假如dp[i][j]对应字符串“abc”以及字符规则“abc”,当字符串“abc”以及字符规则“abc”分别增加一位相同的字母d,即dp[i][j]对应字符串“abcd”以及字符规则“abcd”,前者是匹配的,那么后者也是匹配的。

  • 第二种情况,假如 p[j] = ‘.’ ,由于 ‘.’ 可以替代任务字母,所以不管s[i]等于什么,都可以匹配上,因此转移方程同上:
    dp[i][j] = dp[i-1][j-1]

  • 第三种情况,也是最复杂的情况,当 p[j] = ‘*’ ,这个时候应该分哪些情况考虑呢?
    首先回顾一下 ‘*’的含义: 匹配零个或多个前面的那一个元素,也就是说,重复0次或多次之前的元素。所以 ‘*’与 p[j-1] 的值相关。

    • 当s[i] != p[j-1]时, ‘*’表示重复0次,那么字符规则中的第j个以及第j-1个元素都可去除,只需要考虑长度为i的s与长度为j-2的字符规律p是否匹配即可,有:
      dp[i][j] = dp[i][j-2]

    • 当s[i] = p[j-1] 时,比如存在字符串“#b”以及字符规则“#b*”(注,#表示若干个前面的元素),由于’*'的多样性,这里存在多个子问题,也就是多种转移情况,并且只需要某一个子问题匹配成立,dp[i][j] 便匹配成立:

      • 首先,’*'取多个字符,假如字符串“#”以及字符规则“#b*”匹配,那么字符串“#b”以及字符规则“#b*”也一定匹配,因为 ‘*’可以匹配多加的b,因此子问题一为 dp[i-1][j]
      • 其次,’*'取1个字符,假如字符串“#b”以及字符规则“#b”匹配,那么字符串“#b”以及字符规则“#b*”也一定匹配,因为 ‘b*’可以表示b出现了一次,因此子问题二为 dp[i][j-1]
      • 最后,’*'取0个字符,假如字符串“#b”以及字符规则“#”匹配,那么字符串“#b”以及字符规则“#b*”也一定匹配,因为 ‘b*’可以表示b出现了零次,因此子问题三为 dp[i][j-2]

      综合上述三种情况, dp[i][j] = dp[i-1][j] or dp[i][j-1] or dp[i][j-2]

    • 而当 p[j-1] = ‘.’时,可以匹配任意字符,与上一种情况一样。

代码

class Solution:
    def is_match(self, s: str, p: str) -> bool:
        if not p: return not s
        if not s and len(p) == 1: return False 

        nrow = len(s) + 1
        ncol = len(p) + 1

        dp = [[False for c in range(ncol)] for r in range(nrow)]
        
        dp[0][0] = True
        dp[0][1] = False
        for c in range(2, ncol):
            j = c-1
            if p[j] == '*': dp[0][c] = dp[0][c-2]
        
        for r in range(1, nrow):
            i = r-1
            for c in range(1, ncol):
                j = c-1
                if s[i] == p[j] or p[j] == '.':
                    dp[r][c] = dp[r-1][c-1]
                elif p[j] == '*':
                    if p[j-1] == s[i] or p[j-1] == '.':
                        dp[r][c] = dp[r-1][c] or dp[r][c-2] or dp[r][c-1]
                    else:
                        dp[r][c] = dp[r][c-2]
                else:
                    dp[r][c] = False

        return dp[nrow-1][ncol-1]

你可能感兴趣的:(算法,计算机)