通配符匹配(困难) 给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?' 和 '*' 匹配规则的通配符匹配:
- '?' 可以匹配任何单个字符。
- '*' 可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。
示例 1:
输入:s = "aa", p = "a" 输出:false 解释:"a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:s = "aa", p = "*" 输出:true 解释:'*' 可以匹配任意字符串。
示例 3:
输入:s = "cb", p = "?a" 输出:false 解释:'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
提示:
- 0 <= s.length, p.length <= 2000
- s 仅由小写英文字母组成
- p 仅由小写英文字母、'?' 或 '*' 组成
官方题解思路
说明:
- sp[i][j] 表示字符串 s 的前 i 个字符和模式 p 的前 j 个字符是否能匹配。
- 对比的是s的第i个字符和p的第j个字符:s[i-1],p[j-1]
动态规划 - 解题思路 - 时间复杂度 O(m*n)
- 划分子问题
- 状态转移方程
- 确定边界条件
- 顺序执行,一般由子问题的结果来解决最终问题
- 最终问题:ap[i][j]
- 划分子问题:
- 边界:
sp[0][0] === true,
sp[i][0] === false,
sp[0][j] 当p[j].every(char => char === '*') 为true - 执行顺序:
由子问题的结果来解决最终问题,则 i 和 j 应从 0-n/m
/**
* 复杂度 O(n*m)
*/
function getDP(i, j, s, p, dp) {
if (i === 0 && j === 0) {
return true;
} else if (i === 0 && j > 0) {
return p.slice(0, j).every(char => char === '*');
} else if (i > 0 && j === 0) {
return false;
}
if (p[j-1] === '*') {
return dp[i - 1][j] || dp[i][j - 1];
}
if (p[j-1] === '?' || s[i-1] === p[j-1]) {
return dp[i - 1][j - 1];
}
return false;
}
const isMatch = function(s, p) {
const sLength = s.length;
const pLength = p.length;
const sl = s.split('');
const pl = p.split('');
const dp = [];
for (let i = 0; i <= sLength; i++) {
dp[i] = [];
for (let j = 0; j <= pLength; j++) {
dp[i][j] = getDP(i, j, sl, pl, dp);
}
}
console.log({dp})
return dp[sLength][pLength];
};
贪心算法 - 解题思路 - 时间复杂度O(mlogn)-O(m*n)
- 建立问题的数学模型;
- 把求解的问题分成若干个子问题;
- 寻找这些子问题的局部最优解;
- 把局部最优解合成全局最优解。
- 子问题分析:
p = '∗abcd∗' 字符串 s 中的每个位置作为起始位置,并判断对应的子串是否为 abcd 即可。这种暴力方法的时间复杂度为 O(mn)
p = '∗abcd∗efgh∗i∗' 首先暴力找到最早出现的 abcd,随后从下一个位置开始暴力找到最早出现的 efgh,最后找出 i,这样「贪心地」找到最早出现的子串是比较直观的,因为如果 sss 中多次出现了某个子串,那么我们选择最早出现的位置,可以使得后续子串能被找到的机会更大。
p = 'abcd∗efgh∗i∗opi' split 为 abcd & ∗efgh∗i∗ & opi,s的前4个字符与 abcd , s的后3个字符与opi匹配,中间段同上
p = '' - 局部最优解合成全局最优解:
合成首,尾,中间段,p=""时的结果,得到最终结果
个人初次解题思路
💡 优化后执行时间从288ms -> 88ms
输入s p
s[i] ,p[j]
sp[i][j] // p[j] === '?' | p[j] === s[i]
lastPIndex // 上一个 "" 的p下标
lastSIndex // 匹配到上一个 ""时 的s下标,失败则右移一位作为下轮(与lastP*Index+1之后字符)匹配的开始
优化点:新增 lastSIndexNext 代表下一个与 lastPIndex + 1 匹配的s下标
- 依次对比sp[i][j],直到 i >= s.length
-
- 当遇到 p[j] 为""的时候,记下当前i和j的位置,因为匹配0/n个任意字符,所以i不动,j右移一位
优化点:lastS*IndexNext = undefined - 当遇到 sp[i][j] 为true时,i j分别右移一位
优化点:s[i] === p[lastPIndex+1] && i !== lastSIndex && !lastSIndexNext则 lastSIndexNext = i - 当遇到 sp[i][j] 为false,lastSIndex无值时
代表j之前没有出现过,且出现了不匹配的情况,则直接输出结果 “匹配失败❌” - 当遇到 sp[i][j] 为false,lastSIndex有值时
代表以 lastSIndex 作为开始字符与lastPIndex+1(的下一个字符)匹配的这一轮失败,需要以 lastSIndex+1 作为开始字符 继续 与 lastPIndex+1 之后的字符
优化点:可以优化为记住下一个与lastPIndex+1相同字符的下标,下一轮可以跳到那个记忆点,避免重复匹配,例如 isMatch('abcabczzzde', 'abc???de')
if (lastSIndexNext) {
lastSIndex = lastSIndexNext;
lastSIndexNext = undefined;
} else if (p[lastPIndex+1] !== '?') {
lastS*Index = i;
}
- 当遇到 p[j] 为""的时候,记下当前i和j的位置,因为匹配0/n个任意字符,所以i不动,j右移一位
- 判断 j 之后是否有非“”的字符,若有则代表规则不匹配,若无则代表规则匹配,return p.slice(j).every(char => char === '')
/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/
const isMatch = function(s, p) {
const sList = s.split('');
const pList = p.split('');
let lastStarIndexP;
let lastStarIndexS;
let lastStarIndexSNext;
let j = 0;
let i = 0;
while (i < sList.length) {
if (pList[j] === '*') {
// console.log('lastStarIndexP', i, j, sList[i], pList[j])
lastStarIndexP = j;
lastStarIndexS = i;
lastStarIndexSNext = undefined;
// console.log({lastStarIndexS, lastStarIndexP});
j++;
} else if (pList[j] === '?' || pList[j] === sList[i]) {
// console.log('next', i, j, sList[i], pList[j]);
// 记住s中下一个与*下一个字符对比的下标
if (!lastStarIndexSNext && sList[i] === pList[lastStarIndexP + 1] && i !== lastStarIndexS) {
lastStarIndexSNext = i;
// console.log({lastStarIndexSNext})
}
j++;
i++;
} else if (lastStarIndexP > -1) {
// console.log('retry', i, j, sList[i], pList[j], {lastStarIndexS});
// j 复位到 * 的后一个字符
j = lastStarIndexP + 1;
// i 复位到 lastStarIndexS + 1 || lastStarIndexSNext
if (lastStarIndexSNext) {
lastStarIndexS = lastStarIndexSNext;
lastStarIndexSNext = null;
} else if (p[lastP*Index+1] !== '?') {
lastStarIndexS = i;
} else {
lastStarIndexS++;
}
i = lastStarIndexS;
} else {
// console.log('error', i, j, sList[i], pList[j]);
return false;
}
}
// console.log('exit', {j, p});
return pList.slice(j).every(char => char === '*');
};