题目

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

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

说明:

s 可能为空,且只包含从 a-z 的小写字母。

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

个人分析

拿到题目的第一反应就是这是一个 regex 表达式解析引擎,但是过于复杂。

于是可以按照一定的顺序去实现。

下面来逐步看一下这个题目的解答过程。

v1 标准库实现版本

代码

public boolean isMatch(String s, String p) {
return s.matches(p);
}

性能

Runtime: 64 ms, faster than 24.57% of Java online submissions for Regular Expression Matching.
Memory Usage: 40.3 MB, less than 7.95% of Java online submissions for Regular Expression Matching.

虽然实现了,但是对于我们个人基本没有任何收益。

也没有体会到 regex 解析过程的快乐,而且性能也不怎么样。

v2 递归实现

实现思路

如果 p 中没有任何 * 号,那么对比起来其实比较简单,就是文本 s 和 p 一一对应。

. 对应任意单个字符,也不难。

如果存在 * 号

如果存在 *,这个问题就会难那么一些。

public boolean isMatch(String s, String p) {
// 如果 p 已经遍历结束,直接看 s 是否结束。
if(p.isEmpty()) {
return s.isEmpty();
}

// 第一位是否匹配判断
boolean firstMatch = !s.isEmpty() && (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.');

if(p.length() >= 2 && p.charAt(1) == '*') {
// 1. 第一位匹配 && 后续匹配 (* 一次或者多次)
// 2. c* 出现零次,则直接全文本匹配。
return (firstMatch && isMatch(s.substring(1), p)) || isMatch(s, p.substring(2));
} else {
// 第二位不是 *,则直接跳过第一位看后续的信息。
return firstMatch && isMatch(s.substring(1), p.substring(1));
}
}

效果

这个用到了递归,性能如下:

Runtime: 88 ms, faster than 8.51% of Java online submissions for Regular Expression Matching.
Memory Usage: 39.8 MB, less than 27.13% of Java online submissions for Regular Expression Matching.

一个字,惨~

v3 动态规划

对于递归的思考

你也许发现了,原来的代码中

isMatch(s.substring(1), p.substring(1))

这种类似的匹配结果,实际上是一次次的在重复的。

比如第一次我们匹配 [1, 10],后续又匹配 [2, 10]

这样如果你学过 DP 那么会有一个想法,能否重复利用已经判断过的内容呢?

DP 无敌。

解题思路

我们用递归中同样的回溯方法,除此之外,因为函数 match(text[i:], pattern[j:]) 只会被调用一次,我们用 dp(i, j) 来应对剩余相同参数的函数调用,这帮助我们节省了字符串建立操作所需要的时间,也让我们可以将中间结果进行保存。

自顶向下的方法

这里的核心区别就是不对 text/pattern 做 substring 的操作,而是从前往后处理。

enum Result {
TRUE, FALSE
}

class Solution {

Result[][] memo;

public boolean isMatch(String text, String pattern) {
memo = new Result[text.length() + 1][pattern.length() + 1];
return dp(0, 0, text, pattern);
}

public boolean dp(int i, int j, String text, String pattern) {
if (memo[i][j] != null) {
return memo[i][j] == Result.TRUE;
}
boolean ans;
if (j == pattern.length()){
// 如果 pattern 已经遍历结束
ans = i == text.length();
} else{
// 第一位的判断和原来类似
boolean first_match = (i < text.length() &&
(pattern.charAt(j) == text.charAt(i) ||
pattern.charAt(j) == '.'));

if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
ans = (dp(i, j+2, text, pattern) || first_match && dp(i+1, j, text, pattern));
} else {
ans = first_match && dp(i+1, j+1, text, pattern);
}
}


// 保存临时结果
memo[i][j] = ans ? Result.TRUE : Result.FALSE;
return ans;
}
}

效果

Runtime: 3 ms, faster than 83.97% of Java online submissions for Regular Expression Matching.
Memory Usage: 39.9 MB, less than 22.69% of Java online submissions for Regular Expression Matching.

性能还是不错的。

实际上这个性能是比实现一个 regex 引擎要好的,因为 regex 的编译构建 DFA/NFA 非常的耗时。

自上而下

理解了上面的解法,下面的解法就比较简单了。

从后往前处理,这样就避免掉了默认值的问题,不需要像上面一样引入一个奇奇怪怪的枚举值。

public boolean isMatch(String s, String p) {
//dp 存放的是后面处理的结果
boolean[][] dp = new boolean[s.length()+1][p.length()+1];
dp[s.length()][p.length()] = true;

for(int i = s.length(); i >= 0; i--) {
for(int j = p.length()-1; j >= 0; j--) {
// 核心代码保持不变
// 这里不用判断是否为 empty 的问题
boolean firstMatch = i < s.length() && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
// 判断星号
if(j+1 < p.length() && p.charAt(j+1) == '*') {
// 出现零次
// 一次或者多次
dp[i][j] = dp[i][j+2] || (firstMatch && dp[i+1][j]);
} else {
dp[i][j] = firstMatch && dp[i+1][j+1];
}
}
}

// 直接返回结果
return dp[0][0];
}

效果

还算比较优雅,性能还算满意。

Runtime: 2 ms, faster than 92.84% of Java online submissions for Regular Expression Matching.
Memory Usage: 38.3 MB, less than 73.31% of Java online submissions for Regular Expression Matching.

小结

虽然我们使用过很多次 Regex 正则表达式,但是实际上实现起来可能没有使用那么简单。

后续有机会我们可以讲述写如何实现一个相对完整的正则表达式引擎。

【leetcode】如何实现 regex 正则表达式引擎的更多相关文章

  1. Python的regex模块——更强大的正则表达式引擎

    Python自带了正则表达式引擎(内置的re模块),但是不支持一些高级特性,比如下面这几个: 固化分组    Atomic grouping 占有优先量词    Possessive quantifi ...

  2. DEELX 正则表达式引擎(v1.2)

    DEELX 正则表达式引擎(v1.2) 简介见文末. 选择使用deelx的理由:全部代码位于一个头文件(.h)中, 比任何引擎都使用简单和方便. 利用分组从字符串当中提取出化学元素英文名.比如 Ag, ...

  3. 【C++】正则表达式引擎学习心得

    最近参照一些资料实现了一个非常简易的正则表达式引擎,支持基本的正则语法 | + * ()等. 实现思路是最基本的:正则式->AST->NFA->DFA. 以下是具体步骤: 一. 正则 ...

  4. 实现一个正则表达式引擎in Python(一)

    前言 项目地址:Regex in Python 开学摸鱼了几个礼拜,最近几天用Python造了一个正则表达式引擎的轮子,在这里记录分享一下. 实现目标 实现了所有基本语法 st = 'AS342abc ...

  5. 实现一个正则表达式引擎in Python(二)

    项目地址:Regex in Python 在看一下之前正则的语法的 BNF 范式 group ::= ("(" expr ")")* expr ::= fact ...

  6. 实现一个正则表达式引擎in Python(三)

    项目地址:Regex in Python 前两篇已经完成的写了一个基于NFA的正则表达式引擎了,下面要做的就是更近一步,把NFA转换为DFA,并对DFA最小化 DFA的定义 对于NFA转换为DFA的算 ...

  7. Regex 正则表达式入门

    0,什么是正则表达式 正则表达式(Regular Expression简写为Regex),又称为规则表达式,它是一种强大的文本匹配模式,其用于在字符串中查找匹配符合特定规则的子串. 正则表达式是独立于 ...

  8. 转:C++ Boost/tr1 Regex(正则表达式)快速指南

    C++ Boost/tr1 Regex(正则表达式)快速指南 正则表达式自Boost 1.18推出,目前已经成为C++11(tr1)的标准部分. 本文以Boost 1.39正则表达式为基础,应该广泛适 ...

  9. 正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——3 计算4个函数

    整个引擎代码在github上,地址为:https://github.com/sun2043430/RegularExpression_Engine.git nullable, firstpos, la ...

  10. 正则表达式引擎:nfa的转换规则。

    正则表达式引擎:nfa的转换规则. 正则到nfa 前言 在写代码的过程中,本来还想根据龙书上的说明来实现re到nfa的转换.可是写代码的时候发现,根据课本来会生成很多的无用过渡节点和空转换边,需要许多 ...

随机推荐

  1. STM32CubeMX教程22 FSMC - 8080并行接口TFT-LCD驱动

    1.准备材料 开发板(正点原子stm32f407探索者开发板V2.4) STM32CubeMX软件(Version 6.10.0) 野火DAP仿真器 keil µVision5 IDE(MDK-Arm ...

  2. [转帖]PostgreSQL数据库的版本历史及关键变化

    https://cloud.tencent.com/developer/article/2311843 举报 PostgreSQL是一个强大的开源关系型数据库,它的发展历程充满了创新和卓越的设计.让我 ...

  3. [转帖]浅析SQL数据类型的隐式转换与显式转换以及隐式转换可能导致的问题

    https://www.cnblogs.com/goloving/p/15222604.html 一.隐式类型转换问题 1.隐式类型转换: 比如:SELECT 1 + '1'; 2.隐式类型转换的问题 ...

  4. [转帖]linux系统目录结构介绍

    linux的目录结构 Linux系统各个目录的作用 /: 根目录.有且只有一个根目录.所有的东西都是从根目录开始.举个例子:当你在终端里输入"/home",你其实是在告诉服务器,先 ...

  5. [转帖]UseG1GC垃圾回收技术解析

    https://www.cnblogs.com/yuanzipeng/p/13374690.html 介绍 G1 GC,全称Garbage-First Garbage Collector,通过-XX: ...

  6. 日志框架简介-Slf4j+Logback入门实践 | 京东云技术团队

    前言 随着互联网和大数据的迅猛发展,分布式日志系统和日志分析系统已广泛应用,几乎所有应用程序都使用各种日志框架记录程序运行信息.因此,作为工程师,了解主流的日志记录框架非常重要.虽然应用程序的运行结果 ...

  7. MySQL知识点总结(完整版)

    MySQL学习笔记 登录和退出MySQL服务器 # 登录MySQL $ mysql -u root -p12345612 # 退出MySQL数据库服务器 exit; 基本语法 -- 显示所有数据库 s ...

  8. NetCore高级系列文章03---中间件

    .net web 一路发展过来,经历了 .net webfrom. .net Mvc ..net core不论哪种架构,都会对用户的请求到达服务器后经历一系列类似于管道的处理.在.net webfro ...

  9. SP5464 CT - Counting triangles 题解

    题目翻译 题意 有一个网格,左上角是 \((0,0)\),右上角是 \((x,y)\).求这个网格中一共有多少个等腰直角三角形. 输入 第一行给定一个 \(c\),表示有 \(c\) 组数据. 后面 ...

  10. C#使用命令行打开diskpart修改盘符

    参考链接: https://www.cnblogs.com/k98091518/p/6019296.html https://learn.microsoft.com/zh-cn/windows-ser ...