面试题噩梦之一——LeetCode题目10:正则表达式匹配
原题描述
+
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串s的,而不是部分字符串。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
示例 1
输入:s = "aa", p = "a"
输出:false
示例 2
输入:s = "aa", p = "a*"
输出:true
示例 3
输入:s = "ab", p = ".*"
输出:true
示例 4
输入:s = "aab", p = "c*a*b"
输出:true
示例 5
输入:s = "mississippi", p = "mis*is*p*."
输出:false
原题链接:https://leetcode-cn.com/problems/regular-expression-matching
进入问题
+
竟然有人直接在程序里面调用正则表达式的库?你这样面试绝对过不了的
言归正传,此题挺难,至少我提交代码之后对着bad case调了半天才调通。但这道题确实面试考过,所以我希望你至少在方法上有个印象,能吃透就更好了。
编译原理课程中讲过正则表达式的原理,可以转换为有限自动机问题,这是编程语言词法分析的必备工具。
但你应该注意的是,这道题目是一个非常简易的正则表达式匹配器,用自动机这个工具也不是不可以,但确实有大炮打蚊子的感觉,而且自动机的程序可不好写。而且面试的题,能在原理上复杂到哪去?
我觉得你应该想一想,p的子串和s的子串有没有联系?当然有!
最简单地情况,在不考虑'*'的时候,若p和s能够匹配,那么同时去掉p和s的首个字符之后的两个子串应该也是匹配的。如果考虑'*',这种类似的关系也存在,我们可以先分情况讨论一下这个规律。
思路解析
+
先借用python的一些表达:令 表示字符串 中以第 位开头的子串。
然后我们定义 :表示子串 与 是否匹配,那么 即为所求。
case 1
当 时,
case 2
当 时,
对于公式(2)需要做些解释:当 时,我们要关注它前面一个字符,即 。因为 在 中可以出现0次,也可以出现无数次,这分别对应两种情况。
出现0次的情况,意味着我们可以忽略 和 ,那么 。
出现1次或多次的情况,意味着字符串 也可能和 匹配,但前提是满足 。
到现在为止,你可以编写递归程序了。但题目这么明显的最优子结构也在提醒着你,这是一道练习动态规划的好题目,只不过你需要好好处理一些边界条件。
基本原理你已经知道了,但我还是希望你能够好好的动手写一写,对着bad case调一调加深理解。
复杂度分析
+
- 时间复杂度:
- 空间复杂度:
C++参考代码
+
为什么我要开辟(m+1)*(n+1)长度的数组?因为便于处理空串的情况。我假想每一个模式串和字符串最后一位都是 ,并且也参与匹配判断,那么当给定的p和s中有空串情况的时候,我也可以当做正常字符串去处理。
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size();
int n = p.size();
bool head_match = false;
vector<vector<bool>> dp(m+1, vector<bool>(n+1, true));
for (int i = m; i >= 0; --i) {
for (int j = n; j >= 0; --j) {
if (i < m && j < n) head_match = s[i] == p[j] || p[j] == '.';
else if (i == m && j == n) head_match = true;
else head_match = false;
if (j + 2 <= n && p[j+1] == '*') {
dp[i][j] = dp[i][j+2];
if (i + 1 <= m)
dp[i][j] = dp[i][j] || (dp[i+1][j] && head_match);
} else {
dp[i][j] = head_match;
if (i + 1 <= m && j + 1 <= n)
dp[i][j] = dp[i][j] && dp[i+1][j+1];
}
}
}
return dp[0][0];
}
};
- vue.js如何在标签属性中插入变量参数
- SpringBoot解决ajax跨域问题
- WebBrowser(IE) 与 JS 相互调用
- HOSTS配置问题导致集群异常故障分析
- Linux Regulator Framework(2)_regulator driver
- systemd的作用
- alsa声卡分析alsa-utils调用过程(二)-tinymixer
- alsa声卡分析alsa-utils调用过程(一)-tinyplay
- ALSA声卡驱动的DAPM(二)-建立过程
- ALSA声卡驱动的DAPM(一)-DPAM详解
- 高通Audio中ASOC的codec驱动(二)
- vue项目里的日期格式化
- CentOS下的Mysql的安装和使用
- vue路由跳转传参数
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- springboot 事务,多张表的操作事务回滚
- 移动硬盘显示盘符但是打不开,提示加密
- GORM V2 模型定义、约定、标签
- 字符串:简单的反转还不够!
- TypeScript 实战算法系列(七):实现图的遍历
- 63. Vue MUI的基本使用
- 初学web自动化测试--笔记1
- R语言作图——Line plot with error
- Python自学成才之路 玩转虚拟环境
- 基于腾讯云的 Rust 和 WebAssembly 函数即服务
- 谷歌开源NLP模型可视化工具LIT,模型训练不再「黑箱」
- Python 装饰器填坑指南 | 最常见的报错信息、原因和解决方案
- 社区开源框架预制件相关模块:CollectManager详解
- Kettle构建Hadoop ETL实践(三):Kettle对Hadoop的支持
- 3种 Springboot 全局时间格式化方式,别再写重复代码了