描述

题目描述

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

示例
输入:"aaa","a*a"
返回值:true
引言

知识点:动态规划,递归,正则表达式
难度:⭐⭐⭐⭐


题解

解题思路

对于动态规划解法,总的思路便是状态转移,即不断判断从 str[:1] 和 pattern[:1] ,即从第一个字符开始判断是否匹配,直到整个字符串完全匹配。

如下图的图解,每次状态转移总共有两种状态:(1)str 增加一个字符,判断是否匹配(2)pattern增加一个字符,判断是否匹配

方法一:动态规划

  • 每个字符有三种情况出现:“字符”、”.“、“*”
  • 状态定义:dp[i][j]表示字符串 str的前 i 个字符串和 pattern 的前 j 个字符串是否匹配
  • 状态转移:主要是对 “*” 的状态处理
    • pattern[i - 1] != “*”,即上一位不为*。dp[i][j]在以下任何一种情况为true时,则等于true
      • 1、dp[i - 1][j - 1] && str[i - 1] == pattern[j - 1],即上一个状态和当前位的字符都匹配
      • 2、dp[i - 1][j - 1] && pattern[j - 1] == '.',即上一个状态为true,且pattern上一位包含 ‘.’
    • pattern[i - 1] == “*”,即当前位的上一位为*。dp[i][j]在以下任何一种情况为true时,则等于true
      • 1、dp[i][j - 2],即 j-2 位 的pattern能满足,则 i-1 位是 ‘*’ 作为任意数量的值必定能满足匹配
      • 2、dp[i - 1][j] && str[i - 1] == pattern[j - 2];即让字符 pattern[j-2]多出现几次,看能否匹配
      • 3、dp[i - 1][j] && pattern[j - 2] == '.', 即让字符 '.' 多出现 1 次时,能否匹配;
  • 状态初始化
    • dp[0][0] == true 代表两个空字符串能够匹配。
    • dp[0][j] = dp[0][j - 2]p[j - 1] = '*', 即当前pattern的0到j位是true还是false,取决于dp[0][j-2]是否匹配,以及 pattern的当前位的上一位是否为‘*’,因为‘*’可以匹配任何值,包括空值
  • 返回值
    • dp 矩阵右下角字符,代表字符串 strpattern 能否匹配。
图解

image-20210703003601775

image-20210703010414270

算法流程:
  • 定义状态,并初始化
  • 遍历每个str和pattern中的字符
    • 1、当比较的位pattern[j-1]=='.', 或者字符相等匹配,则状态取决于上一次状态
    • 2、对于包含 * 的匹配
      • 当之前位为 '.', 或者字符相等,则匹配
      • 否则只能不匹配
  • 返回状态转移最后的结果
Java 版本代码如下:
import java.util.*;
public class Solution {
    public boolean match (String str, String pattern) {
        int strLen = str.length() + 1, patternLen = pattern.length() + 1;
        // 定义状态,并初始化
        boolean[][] dp = new boolean[strLen][patternLen];
        dp[0][0] = true;
        // 初始化首行
        for(int j = 2; j < patternLen; j++) {
            // 因为pattern[j-1]的 * 可以取任意值包括空值,因此dp[0][j]相当于取决于dp[0][j-2]
            if(pattern.charAt(j - 1) == '*') {
                dp[0][j] = dp[0][j - 2];
            }
        }
        // 状态转移
        for(int i = 1; i < strLen; i++) {
            for(int j = 1; j < patternLen; j++) {
                // 当比较的位pattern[j-1]=='.', 或者字符相等匹配,则状态取决于上一次状态
                if (pattern.charAt(j - 1) == '.' || str.charAt(i - 1) == pattern.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];   
                } else if (j >= 2 && pattern.charAt(j - 1) == '*'){
                    // 对于包含 * 的匹配
                    // 当之前位为 '.', 或者字符相等,则匹配
                    if (pattern.charAt(j - 2) == '.' || pattern.charAt(j - 2) == str.charAt(i - 1)) {
                        dp[i][j] = dp[i][j - 2] || dp[i - 1][j - 2] || dp[i - 1][j];
                    } else {
                        // 否则只能不匹配
                        dp[i][j] = dp[i][j - 2];
                    }
                }
            }
        }
        return dp[strLen - 1][patternLen - 1];
    }
}
Python 版本代码如下:
class Solution:
    def match(self, s, p):
        if not s and not p:
            return True
        m, n = len(s) + 1, len(p) + 1  # 两个字符串的长度
        dp = [[False] * n for i in range(m)]
        # 初始化
        dp[0][0] = True
        for j in range(2, n, 2):
            dp[0][j] = dp[0][j - 2] and p[j - 1] == '*'
        # 状态转移
        for i in range(1, m):
            for j in range(1, n):
                if p[j - 1] == '*':
                    if dp[i][j - 2]: dp[i][j] = True
                    elif dp[i - 1][j] and s[i - 1] == p[j - 2]: dp[i][j] = True
                    elif dp[i - 1][j] and p[j - 2] == '.': dp[i][j] = True
                else:
                    if dp[i - 1][j - 1] and s[i - 1] == p[j - 1]: dp[i][j] = True
                    elif dp[i - 1][j - 1] and p[j - 1] == '.': dp[i][j] = True
        return dp[-1][-1]
复杂度分析:

时间复杂度 O(SP):其中 S,P 分别为 strpattern 的长度,状态转移需遍历整个 dp 矩阵
空间复杂度 O(SP):状态二维矩阵 dp