大家好,这篇文章用来记录 LeetCode 76 最小覆盖子串 的 JS 标准解法,这道题是滑动窗口的经典必做题,面试频率极高。
我会直接给出可 AC 代码,并逐行详细解释,方便自己复习也分享给大家。
题目简介
给两个字符串 s 和 t,找出 s 中包含 t 所有字符的最短子串 。 如果没有,返回空字符串 ""。
完整可提交代码
javascript
var minWindow = function(s, t) {
const need = {};
const window = {};
let valid = 0;
let left = 0, right = 0;
let start = 0, len = Infinity;
// 统计 t 中每个字符需要的数量
for (const c of t) {
need[c] = (need[c] || 0) + 1;
}
// 需要凑齐的字符种类数
const needSize = Object.keys(need).length;
// 右指针遍历整个字符串
while (right < s.length) {
const c = s[right];
right++;
// 如果当前字符是我们需要的
if (need[c]) {
window[c] = (window[c] || 0) + 1;
// 某一类字符数量达标,valid+1
if (window[c] === need[c]) {
valid++;
}
}
// 当窗口已经满足所有条件,开始收缩左侧
while (valid === needSize) {
// 更新最小窗口
if (right - left < len) {
start = left;
len = right - left;
}
// 准备移除左指针字符
const d = s[left];
left++;
// 如果是需要的字符,判断是否会破坏 valid
if (need[d]) {
if (window[d] === need[d]) {
valid--;
}
window[d]--;
}
}
}
// 没有找到返回空,否则返回截取的子串
return len === Infinity ? "" : s.slice(start, start + len);
};
逐行详细解析
1. 变量定义
javascript
const need = {}; // 记录 t 中每个字符需要多少个
const window = {}; // 记录当前窗口里每个字符有多少个
let valid = 0; // 记录已经"数量达标"的字符种类数
let left = 0, right = 0; // 滑动窗口双指针
let start = 0, len = Infinity; // 记录最终最短子串的起点和长度
need:我们要找的字符目标数量window:当前窗口内的字符数量valid:核心判断条件,表示有多少种字符已经满足数量要求left/right:窗口左、右边界start/len:保存最优解,避免反复截取字符串
2. 统计目标字符
javascript
for (const c of t) {
need[c] = (need[c] || 0) + 1;
}
const needSize = Object.keys(need).length;
- 遍历
t,统计每个字符需要多少个 needSize:一共需要凑齐多少种字符
3. 右指针扩大窗口
javascript
while (right < s.length) {
const c = s[right];
right++;
if (need[c]) {
window[c] = (window[c] || 0) + 1;
if (window[c] === need[c]) {
valid++;
}
}
- 右指针一直往右走,扩大窗口
- 遇到需要的字符 ,就加入
window计数 - 当某字符数量刚好达标 时,
valid += 1
4. 左指针收缩窗口(核心)
javascript
while (valid === needSize) {
// 更新最小窗口
if (right - left < len) {
start = left;
len = right - left;
}
const d = s[left];
left++;
if (need[d]) {
if (window[d] === need[d]) {
valid--;
}
window[d]--;
}
}
- 当
valid === needSize,说明当前窗口已经包含了 t 所有字符 - 这时尽可能缩小窗口,寻找更短的子串
- 每次移动左指针前:
- 先更新最小窗口记录
- 再把左边字符移出窗口
- 如果移出后导致某字符不满足数量 ,
valid--,退出循环
5. 返回结果
javascript
return len === Infinity ? "" : s.slice(start, start + len);
- 如果
len还是无穷大,说明没找到,返回空串 - 否则返回记录的最短子串
核心思想总结
这道题的核心就是 滑动窗口 + 哈希计数:
- 用右指针扩大窗口,直到满足条件
- 用左指针收缩窗口,直到不满足条件
- 全程记录最小窗口
- 用
valid精准判断窗口是否合法
这套模板可以通杀绝大多数子串滑动窗口题,非常实用。
测试用例
javascript
console.log(minWindow("ADOBECODEBANC", "ABC")); // "BANC"
console.log(minWindow("a", "a")); // "a"
console.log(minWindow("a", "aa")); // ""