Leetcode刷题:正则表达式匹配

684 阅读3分钟

这是我参与新手入门的第2篇文章。

一、题目描述

给定一个字符串 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
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。

思路分析

题目中的匹配是一个「逐步匹配」的过程:我们每次从字符串 p 中取出一个字符或者「字符 + 星号」的组合,并在 s 中进行匹配。对于 p 中一个字符而言,它只能在 s 中匹配一个字符,匹配的方法具有唯一性;而对于 p 中字符 + 星号的组合而言,它可以在 s 中匹配任意自然数个字符,并不具有唯一性。因此我们可以考虑使用动态规划,对匹配的方案进行枚举。

  • 这道题考察了如何利用动态规划的思想来找到问题的最优解决方式,可用dp[i][j] 表示 s 的前 i 个是否能被 p 的前 j 个匹配。首先想的时候从已经求出了 dp[i-1][j-1] 入手,再加上已知 s[i]、p[j],要想的问题就是怎么去求 dp[i][j]。

  • 第一个难想出来的点:怎么区分 * 的两种讨论情况首先给了 * ,明白 * 的含义是 匹配零个或多个前面的那一个元素,所以要考虑他前面的元素 p[j-1]。* 跟着他前一个字符走,前一个能匹配上 s[i],* 才能有用,前一个都不能匹配上 s[i],* 也无能为力,只能让前一个字符消失,也就是匹配 0 次前一个字符。

  • 第二个难想出来的点:怎么判断前面是否匹配 dp[i][j] = dp[i-1][j] // 多个字符匹配的情况 or dp[i][j] = dp[i][j-1] // 单个字符匹配的情况 or dp[i][j] = dp[i][j-2] // 没有匹配的情况

  • 思路有了接下来就是代码实现,由于本身从事前端行业对JavaScript比较熟悉所以就用JS代码来实现了

三、AC代码

const isMatch = (s, p) => {
  if (s == null || p == null) return false;

  const sLen = s.length, pLen = p.length;

  const dp = new Array(sLen + 1);
  for (let i = 0; i < dp.length; i++) {
    dp[i] = new Array(pLen + 1).fill(false); // 将项默认为false
  }
  // base case
  dp[0][0] = true;
  for (let j = 1; j < pLen + 1; j++) {
    if (p[j - 1] == "*") dp[0][j] = dp[0][j - 2];
  }
  // 迭代
  for (let i = 1; i < sLen + 1; i++) {
    for (let j = 1; j < pLen + 1; j++) {

      if (s[i - 1] == p[j - 1] || p[j - 1] == ".") {
        dp[i][j] = dp[i - 1][j - 1];
      } else if (p[j - 1] == "*") {
        if (s[i - 1] == p[j - 2] || p[j - 2] == ".") {
          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[sLen][pLen]; // 长sLen的s串 是否匹配 长pLen的p串
};

四、总结

本题的题目比较容易误导人们去研究如何进行字符串的匹配,从而忽略了使用动态规划算法来实现,但只要想到题目中的匹配是一个「逐步匹配」的过程,从已经求出了 dp[i-1][j-1] 入手,再加上已知 s[i]、p[j],要想的问题就是怎么去求 dp[i][j]就自然而然的想到了动态规划算法实现。