【刷题记录】10. 正则表达式匹配

162 阅读1分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

一、题目描述

来源:力扣(LeetCode)

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

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

  示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 1 <= s.length <= 20
  • 1 <= p.length <= 30
  • s 只包含从 a-z 的小写字母。
  • p 只包含从 a-z 的小写字母,以及字符 . 和 *。 保证每次出现字符 * 时,前面都匹配到有效的字符

二、思路分析

这个题目我可以利用动态规划的思想来解决:

  • 首先我们定义dp[i][j] 表示 s 的前 i 个是否能被 p 的前 j 个匹配

  • 如果 p 的第 j 个字符是一个字母

    • 如果 s 的第 i 个字符与 p 的第 j 个字符不相同,那么无法进行匹配,不符合了。
    • 如果前面的都匹配的同时,且  s 的第 i 个字符与 p 的第 j 个字符相同,即 dp[i-1][j-1] = dp[i,j] && s[i]=p[j]
  • 如果 p 的第 j 个字符是 *, 那么我们就是对 前面一个字符p[j-1]就行任意次数的匹配

    • 0 次就是 dp[i][j] == dp[i][j-2]
    • 1 次就是 dp[i][j] == dp[i−1][j−2] && s[i] == p[j-1]
    • 2 次就是 dp[i][j] == dp[i−2][j−2] && s[i-1]s[i] == p[j-1]
    • .... 总结就是
    dp[i,j]={dp[i][j2],whens[i]p[j1]dp[i1][j] or dp[i][j2],whens[i]==p[j1] dp[i,j]= \begin{cases} dp[i][j-2] ,when & s[i] \neq p[j-1] \\ dp[i−1][j] or dp[i][j−2],when & s[i]==p[j−1] \\ \end{cases}
  • 如果 p[j] 为 '.':匹配的条件是前面的字符匹配, s 中的第 i 个字符可以是任意字符。即 dp[i,j] = dp[i - 1, j - 1] && p[j] == '.'

  • 动态规划的边界条件为 f[0][0]=truef[0][0]=true,即两个空字符串是可以匹配的。最终的答案即为 f[m][n]f[m][n],其中 mn 分别是字符串 sp 的长度

三、代码实现

class Solution {
    public boolean isMatch(String s, String p) {
        int m = s.length();
        int n = p.length();

        //dp[i][j] s 中的 1~i 字符 和 p 中的 1~j 字符 是否匹配
        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;
        for (int i = 0; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (p.charAt(j - 1) == '*') {
                    dp[i][j] = dp[i][j - 2];
                    if (matches(s, p, i, j - 1)) {
                        dp[i][j] = dp[i][j] || dp[i - 1][j];
                    }
                } else {
                    if (matches(s, p, i, j)) {
                        dp[i][j] = dp[i - 1][j - 1];
                    }
                }
            }
        }
        return dp[m][n];
    }

    public boolean matches(String s, String p, int i, int j) {
        if (i == 0) {
            return false;
        }
        if (p.charAt(j - 1) == '.') {
            return true;
        }
        return s.charAt(i - 1) == p.charAt(j - 1);
    }
 }

复杂度分析

复杂度分析

  • 时间复杂度:O(mn)O(mn),其中 mn 分别是字符串 sp 的长度,每个状态在进行转移时的时间复杂度为 O(1)O(1)

  • 空间复杂度:O(mn)O(mn),存储所有状态使用的空间。

运行结果

image.png

总结

这道题的重点是 动态规划 的运用

而动态规划的解题核心主要分为两步:

  1. 第一步:状态的定义
  2. 第二步:状态转移方程的定义

状态表示的是我们在求解问题之中,对问题的分析和转化

运用动态规划我们将一个大问题转化成几个小问题,求解小问题,然后推出大问题的解