[leetcode 47] 通配符匹配 动态规划vs贪心回溯
题目
给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。
- '?' 可以匹配任何单个字符。
- '*' 可以匹配任意字符串(包括空字符串)。
- 两个字符串完全匹配才算匹配成功。
- s 可能为空,且只包含从 a-z 的小写字母。
- p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
动态规划
考虑匹配的基本情形:
- 字符相同
- 模式字符为"?"
- 模式字符为"*"
- 其他不匹配情况
如果我们取s和p的最后一个字符比较,其中字符匹配和遇到"?"时,整个字串的匹配结果依赖于前面s[n-1]和p[n-1]个字符的匹配结果。如果遇到"",则依赖前面相邻位置的匹配结果。因此可以建立一个二维矩阵,通过遍历建立整个矩阵的各个位置匹配结果。
这里比较不容易理解的一点,就是当遇到""时,我们的dp[i][j]到底应该如何取值
dp[i][j] = dp[i-1][j] || dp[i][j-1] // i为s索引,j为p索引
比较重要的是要考虑遍历的过程,先遍历s,为每一段s遍历p,实际上是为每一小段s去匹配所有的p
如果“”匹配0个字符,很容易想到dp[i][j] = dp[i][j-1]
;
匹配一个或多个字符时,容易得到dp[i][j] = dp[i-1][j-1]
。然而,我们总是先对s[:i-1]遍历完整个p,这个时候如果dp[i-1][j-1]为true,由于p[j]='',dp[i-1][j]必为true。而此时按照前面的算法,会得到 dp[i-1][j]=dp[i-2][j-1]
,可能为false。因此我们把这个*看成是前一段s匹配0个字符,得到dp[i][j] = dp[i-1][j-1] = dp[i-1][j]
/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/
var isMatch = function(s, p) {
// 动态规划
let row = new Array(p.length+1).fill(false);
let dp = [];
// 初始化
for (let i=0;i<=s.length;i++) {
dp.push(Object.assign([], row));
}
// 首位匹配
dp[0][0] = true;
for (let i=0;i<p.length;i++) {
if (p[i] == '*') {
dp[0][i+1] = dp[0][i];
}
}
for (let i=0;i<s.length;i++) {
for (let j=0;j<p.length;j++) {
if (s[i]==p[j] || p[j]=='?') {
dp[i+1][j+1] = dp[i][j];
} else if (p[j] == '*') {
// 匹配0个字符 dp[i+1][j]
// 匹配一个或多个字符 dp[i][j+1] ?
dp[i+1][j+1] = dp[i+1][j] || dp[i][j+1];
}
}
}
// console.log(dp);
return dp[s.length][p.length];
};
贪心双指针回溯
动态规划需要生成整个二维矩阵,因此可以优化,只对出现号的位置进行检查。这里必须理解的一点是,当出现多个时,我们只需要对最后一个进行回溯匹配。
比如p = ABC,如果匹配成功,则 s 中必须有一个或多个B,比如 AxByBzC,我们匹配到AB与AxB成功,之后遇到了新的,那么我们只需要对后一个进行回溯,检查C与yBzC。不需要再考虑AB匹配AxByB成功的情况,理解这一点非常重要。
一旦上述概念理解之后,
算法就非常好写了:
- s索引i,p索引j,s的回溯位置pre,p的*回溯位置star
s[i] == p[j] 或 p[j] == '?'
, i,j递增- 遇到,记录的位置star,s的回溯位置pre。*匹配0个字符即i++
- 字符不匹配且不是,如果字符串有,s回溯下一位,p回溯*的下一位
- 字符串没有*,返回false
/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/
var isMatch = function(s, p) {
// <贪心含义>理解只需要回溯匹配最后一个*
let s_idx = 0,p_idx = 0;
let s_pre_idx = -1,p_star_idx = -1;
while (s_idx < s.length) {
if (s[s_idx] == p[p_idx] || p[p_idx]=='?') {
s_idx++;
p_idx++;
} else if (p_idx < p.length && p[p_idx] == '*') {
// 首次匹配0个字符
s_pre_idx = s_idx;
p_star_idx = p_idx;
p_idx++;
} else if (p_star_idx !== -1) {
// p回溯到*的下一个位置,s_pre递增
p_idx = p_star_idx+1;
s_pre_idx++;
s_idx = s_pre_idx;
} else {
return false;
}
}
// 剩下的p应该全是*
while (p_idx < p.length) {
if (p[p_idx] !== '*') {
return false;
}
p_idx++;
}
return true;
};
算法效率
第一次提交是动态规划,第二次是贪心双指针。说实话没想到会差距这么大
其他算法
正则表达式的常规思路是有限状态机,实现一个状态机节点,然后根据模式串p构造一个有向图,如果能够从起点走到终点就表示匹配成功。
原文地址:https://www.cnblogs.com/dapianzi/p/12561468.html
- ASP.NET MVC 2 转换工具
- 使用Sysinternals工具定时休眠Windows Server 2008 R2
- Android中BroadcastReceiver广播
- 启用Windows 7/2008 R2 XPS Viewer
- Spring历史版本变迁和如今的生态帝国
- Android中Services之异步IntentService
- 使用GitHub搭建个人博客
- 这个用来玩儿游戏的算法,是谷歌收购DeepMind的最大原因
- asp.net安全检测工具 --Padding Oracle 检测
- Android中Services简析
- VUE 入门基础(2)
- VUE 入门基础(1)
- AndroidManifest.xml配置文件 android.theme大全权限设置Android Permission中英对照
- Reactive框架:简化异步及事件驱动编程
- 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 数组属性和方法
- 2017 年ICPC 中国大陆区域赛铜牌题解
- 搜索(DFS BFS)专题练习
- AtCoder Beginner Contest 171
- AtCoder Beginner Contest 173 A ~ F(已经补完)
- AtCoder Beginner Contest 174 A ~ E
- AtCoder Beginner Contest 170
- 【队伍训练3】Codeforces Round #661 (Div. 3)
- 购物
- 指纹锁(自定义下比较重载下set的圆括号比较)
- 糖糖别胡说,我真的不是签到题目
- 分数线划定
- 小C的记事本
- 简单的数据结构 (deque的应用)
- [HNOI2003]激光炸弹 (二维前缀和)
- 链表(实现插入一个数)