滑动窗口的解题思路
- 我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。
- 我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的字符串符合要求(包含了 t 中的所有字符)
- 我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果
- 重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头
解题框架:
// 右移窗口
right++;
if (need[c]) {
// 当前字符在需要的字符中,则更新当前窗口统计
window[c] = (window[c] || 0) + 1;
if (window[c] == need[c]) {
// 当前窗口和需要的字符匹配时,验证数量增加1
valid++;
}
}
// 当验证数量与需要的字符个数一致时,就应该收缩窗口了
while (valid == Object.keys(need).length) {
// 更新最小覆盖子串
if (right - left < len) {
start = left;
len = right - left;
}
//即将移出窗口的字符
let d = s[left];
// 左移窗口
left++;
if (need[d]) {
if (window[d] == need[d]) {
valid--;
}
window[d]--;
}
}
}
return len == Number.MAX_VALUE ? "" : s.substr(start, len);
};
Leetcode 209 长度最小的子数组
/*
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function(target, nums) {
let len=nums.length;
let left=0,right=-1;//初始希望滑动窗里没有任何元素
let res=len+1;
let sum=0;
while(left<len){
if(right+1<len&&sum<target) sum+=nums[++right];
else sum-=nums[left++];
if(sum>=target) res=Math.min(res,right-left+1);
}
if(res===len+1) return 0;
return res;
};
Leetcode 3 无重复字符的最长字串
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
const occ = new Set();
const n = s.length;
let rk = -1, ans = 0;
for (let i = 0; i < n; ++i) {
if (i != 0) occ.delete(s[i-1]);
while (rk + 1 < n && !occ.has(s[rk + 1])) {
occ.add(s[rk + 1]);
++rk;
}
ans = Math.max(ans, rk - i + 1);
}
return ans;
};
Leetcode 438 找到字符串中所有字母异位词
根据题目要求,我们需要在字符串 ss 寻找字符串 pp 的异位词。因为字符串 pp 的异位词的长度一定与字符串 pp 的长度相同,所以我们可以在字符串 ss 中构造一个长度为与字符串 pp 的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;当窗口中每种字母的数量与字符串 pp 中每种字母的数量相同时,则说明当前窗口为字符串 pp 的异位词。
var findAnagrams = function(s, p) {
const sLen = s.length, pLen = p.length;
if (sLen < pLen) {
return [];
}
const ans = [];
const sCount = new Array(26).fill(0);
const pCount = new Array(26).fill(0);
for (let i = 0; i < pLen; ++i) {
++sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
++pCount[p[i].charCodeAt() - 'a'.charCodeAt()];
}
if (sCount.toString() === pCount.toString()) {
ans.push(0);
}
for (let i = 0; i < sLen - pLen; ++i) {
--sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
++sCount[s[i + pLen].charCodeAt() - 'a'.charCodeAt()];
if (sCount.toString() === pCount.toString()) {
ans.push(i + 1);
}
}
return ans;
};
Leetcode 76 最小覆盖子串
/**
* @param {string} s
* @param {string} t
* @return {string}
*/
const minWindow = (s, t) => {
let minLen = s.length + 1;
let start = s.length; // 结果子串的起始位置
let map = {}; // 存储目标字符和对应的缺失个数
let missingType = 0; // 当前缺失的字符种类数
for (const c of t) { // t为baac的话,map为{a:2,b:1,c:1}
if (!map[c]) {
missingType++; // 需要找齐的种类数 +1
map[c] = 1;
} else {
map[c]++;
}
}
let l = 0, r = 0; // 左右指针
for (; r < s.length; r++) { // 主旋律扩张窗口,超出s串就结束
let rightChar = s[r]; // 获取right指向的新字符
if (map[rightChar] !== undefined) map[rightChar]--; // 是目标字符,它的缺失个数-1
if (map[rightChar] == 0) missingType--; // 它的缺失个数新变为0,缺失的种类数就-1
while (missingType == 0) { // 当前窗口包含所有字符的前提下,尽量收缩窗口
if (r - l + 1 < minLen) { // 窗口宽度如果比minLen小,就更新minLen
minLen = r - l + 1;
start = l; // 更新最小窗口的起点
}
let leftChar = s[l]; // 左指针要右移,左指针指向的字符要被丢弃
if (map[leftChar] !== undefined) map[leftChar]++; // 被舍弃的是目标字符,缺失个数+1
if (map[leftChar] > 0) missingType++; // 如果缺失个数新变为>0,缺失的种类+1
l++; // 左指针要右移 收缩窗口
}
}
if (start == s.length) return "";
return s.substring(start, start + minLen); // 根据起点和minLen截取子串
};