【leetcode】如何实现 regex 正则表达式引擎
题目
给你一个字符串 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 正则表达式引擎的更多相关文章
- Python的regex模块——更强大的正则表达式引擎
Python自带了正则表达式引擎(内置的re模块),但是不支持一些高级特性,比如下面这几个: 固化分组 Atomic grouping 占有优先量词 Possessive quantifi ...
- DEELX 正则表达式引擎(v1.2)
DEELX 正则表达式引擎(v1.2) 简介见文末. 选择使用deelx的理由:全部代码位于一个头文件(.h)中, 比任何引擎都使用简单和方便. 利用分组从字符串当中提取出化学元素英文名.比如 Ag, ...
- 【C++】正则表达式引擎学习心得
最近参照一些资料实现了一个非常简易的正则表达式引擎,支持基本的正则语法 | + * ()等. 实现思路是最基本的:正则式->AST->NFA->DFA. 以下是具体步骤: 一. 正则 ...
- 实现一个正则表达式引擎in Python(一)
前言 项目地址:Regex in Python 开学摸鱼了几个礼拜,最近几天用Python造了一个正则表达式引擎的轮子,在这里记录分享一下. 实现目标 实现了所有基本语法 st = 'AS342abc ...
- 实现一个正则表达式引擎in Python(二)
项目地址:Regex in Python 在看一下之前正则的语法的 BNF 范式 group ::= ("(" expr ")")* expr ::= fact ...
- 实现一个正则表达式引擎in Python(三)
项目地址:Regex in Python 前两篇已经完成的写了一个基于NFA的正则表达式引擎了,下面要做的就是更近一步,把NFA转换为DFA,并对DFA最小化 DFA的定义 对于NFA转换为DFA的算 ...
- Regex 正则表达式入门
0,什么是正则表达式 正则表达式(Regular Expression简写为Regex),又称为规则表达式,它是一种强大的文本匹配模式,其用于在字符串中查找匹配符合特定规则的子串. 正则表达式是独立于 ...
- 转:C++ Boost/tr1 Regex(正则表达式)快速指南
C++ Boost/tr1 Regex(正则表达式)快速指南 正则表达式自Boost 1.18推出,目前已经成为C++11(tr1)的标准部分. 本文以Boost 1.39正则表达式为基础,应该广泛适 ...
- 正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——3 计算4个函数
整个引擎代码在github上,地址为:https://github.com/sun2043430/RegularExpression_Engine.git nullable, firstpos, la ...
- 正则表达式引擎:nfa的转换规则。
正则表达式引擎:nfa的转换规则. 正则到nfa 前言 在写代码的过程中,本来还想根据龙书上的说明来实现re到nfa的转换.可是写代码的时候发现,根据课本来会生成很多的无用过渡节点和空转换边,需要许多 ...
随机推荐
- Oracle数据库期末考试--学堂在线
1.单选题 (2分) 在Oracle数据库中,下面哪类索引最适合SQL范围查找? 2.单选题 (2分) 在创建Oracle数据库表时,下面哪个元素不出现在CREATE TABLE语句中? 3.单选题 ...
- CS2打开可以听到声音,但黑屏问题?
1.问题 我这里原先是可以启动CS2的,但是后来在CS2中重新调整了分辨率等等,之后由于某种原因又调整了屏幕分辨率,导致后面一进入CS2登录界面,橙色登陆界面就会缩在左上角一小块,并且之后就会陷入黑屏 ...
- css - 使用 " dl、dt、dd " 描述列表的形式 , 实现 【图片加下方文字】 的快速布局
上效果图 : 上代码 : <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...
- [转帖]Oracle 如何列出正在运行的定时任务
https://geek-docs.com/oracle/oracle-questions/569_oracle_how_can_i_list_the_scheduled_jobs_running_i ...
- [转帖]Sqlserver数据库中char、varchar、nchar、nvarchar的区别及查询表结构
https://www.cnblogs.com/liuqifeng/p/10405121.html varchar 和 nvarchar区别: varchar(n)长度为 n 个字节的可变长度且非 U ...
- [转帖]下载 SQL Server Management Studio (SSMS)
https://learn.microsoft.com/zh-CN/sql/ssms/download-sql-server-management-studio-ssms?view=sql-serve ...
- SkyWalking的学习之二(性能优化以及log)
SkyWalking的学习之二(性能优化以及log) 背景 周六在家学习了SkyWalking的交单部署和agent的方式获取日志. 万恶的周天上班到公司发现出现了宕机. 具体原因是我想进行SkyWa ...
- [转帖]【Redis】Redis中使用Lua脚本
Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能. Lua具体语法参考:https://www.runoob. ...
- [转帖]Docker相关的概念和原理
https://www.jianshu.com/p/9737cbe33304 chroot chroot就是可以改变某进程的根目录,使这个程序不能访问目录之外的其他目录.Docker是利用Linux的 ...
- golang实现的 https 协议的四层代理和七层代理
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 四层代理 在 tcp 这一层转发很简单. http 协议是 ...