目录:算法日记
题目来源:10. 正则表达式匹配
题目描述
给你一个字符串 s
和一个字符规律 p
,请你来实现一个支持 '.'
和 '*'
的正则表达式匹配。
'.'
匹配任意单个字符'*'
匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖整个字符串s
的,而不是部分字符串。
题目示例
输入: s = "ab", p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
数据范围
s
只包含从a-z
的小写字母。p
只包含从a-z
的小写字母,以及字符.
和*
。- 保证每次出现字符
*
时,前面都匹配到有效的字符
算法思路
两个字符串间匹配问题考虑动态规划序列模型,使用闫式DP法分析,确保状态不重不漏。
如上图所示,dp[i][j]表示字符串s
中前i
个字符与字符串p
中前j
个字符是否匹配。若dp[i][j]
为true
说明存在匹配的方案。需要特别注意,'*'
需与前一位字符配合使用。
接下来考虑状态计算。设当前字符串s
中第i
个字符与字符串p
中第j
个字符进行匹配。由于'*'
涉及情况最多,因此对'*'
分情况讨论。
- 若不存在
'*'
:dp[i-1][j-1]
有合法方案,且s[i]
与p[j]
匹配,则存在dp[i][j]
合法方案;
- 若存在
'*'
:- 若
'*'
表示0个字符,dp[i][j-2]
; - 当
'*'
表示1个字符:dp[i-1][j-2]
,且s[i]
与p[j-1]
匹配; - 当
'*'
表示2个字符:dp[i-2][j-2]
,且s[i-1]
与p[j-1]
匹配,且s[i]
与p[j-1]
匹配; - ...
- 这里借鉴完全背包问题优化思路,对于当前状态
dp[i][j]
,dp[i-1][j]
已知,且对于状态dp[i-1][j]
有:- 若
'*'
表示0个字符,dp[i-1][j-2]
; - 若
'*'
表示1个字符,dp[i-2][j-2]
,且s[i-1]
与p[j-1]
匹配; - ...
- 由上述推导可知,
dp[i-1][j]
状态可通过添加s[i]
与p[j-1]
匹配关系,来表示上述2-4之间的状态。
- 若
- 2-4之间状态可优化为:
dp[i-1][j]
有合法方案,且s[i]
与p[j-1]
匹配,则存在dp[i][j]
合法方案;
- 若
AC代码
/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/
var isMatch = function(s, p) {
let n = s.length;
let m = p.length;
s = ' ' + s;
p = ' ' + p;
const dp = new Array(n + 1).fill(0).map(x => new Array(m + 1).fill(false));
dp[0][0] = true;
for(let i = 0; i <= n; ++i) {
//p长度为0时必然匹配失败,从1开始
for(let j = 1; j <= m; ++j) {
//*必须与前一位字符配合使用
if(j + 1 <= m && p[j + 1] === '*') continue;
if(p[j] !== '*') {
dp[i][j] = i && dp[i - 1][j - 1] && (p[j] === s[i] || p[j] === '.');
} else {
dp[i][j] = dp[i][j - 2] || i && dp[i - 1][j] && (p[j - 1] === s[i] || p[j - 1] === '.');
}
}
}
return dp[n][m];
};