所谓的滑动窗口,就是通过不断调整指针的起始位置和终止位置,从而得出想要的结果,我们先来看一道算法题
Leetcode 209 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
最直接的做法就是用双重循环,相当于内层循环每次都找出本次外循环变量位置之后的所有子数组,再比较其大小。
function minSubArray(s, nums) {
let result = Infinity;
let sum = 0;
let subLength = 0;
for (let i = 0; i < nums.length; i++) {
sum = 0;
for (let j = i; j < nums.length; j++) {
sum += nums[j];
if (sum >= s) {
subLength = j - i + 1;
result = Math.min(result, subLength);
break;
}
}
}
return result === Infinity ? 0 : result;
}
这种解法的时间复杂度是O(n²),那么有没有一种办法用一次循环就遍历完整个数组,找到最小的数组长度呢,这种办法就是滑动窗口。
双重循环时,我们用一个for表示起始位置,另一个表示终止位置,在滑动窗口中,我们想用一重循环就完成这个任务,那么循环变量就必须能表示整个范围,也就是必须是可以表示终止位置,先来看一下代码。
function minSubArray(s, nums) {
let result = Infinity;
let sum = 0;
let subLength = 0;
let i = 0;
for (let j = 0; j < nums.length; j++) {
sum += nums[j];
//当和满足条件时 缩小窗口 直到不满足条件为止
while (sum >= s) {
//计算子串长度
subLength = j - i + 1;
// 将更小的赋值给result
result = Math.min(result, subLength);
// 当缩小窗口时 总和是不断减小的
// 这种滑动窗口也被称为最小滑窗
sum -= nums[i];
i++;
}
}
return result === Infinity ? 0 : result;
}
这里引用一张代码随想录的滑窗图片。
根据子序列情况,通过不断调整起始位置 ,就是滑动窗口的要点
再看几道相关的题。
leetcode 76 最小覆盖子串
var minWindow = function(s, t) {
let left = 0 ,right = 0
let substr = ''
let res = ''
let need = new Map()
// 首先统计子串中的字母种类和数量 利用map统计
for(let c of t){
need.set(c,need.has(c)?need.get(c)+1:1)
}
// 需要的字母种类 就是map的长度
let needType = need.size
while(right < s.length){
const c = s[right]
// 在窗口扩大时 如果有子串需要的字母 就减少需要的量
if(need.has(c)){
need.set(c,need.get(c)-1)
if(need.get(c)===0){
needType-=1
}
}
// 当子串需求都满足了 缩小窗口
while(needType===0){
//
const c2 = s[left]
substr = s.substring(left,right+1)
if(!res||substr.length<res.length){
res = substr
}
// 缩小窗口时如果是需要的字母 那么再增加需要的字母数量
if(need.has(c2)){
need.set(c2,need.get(c2)+1)
if(need.get(c2)){
needType+=1
}
}
left++
}
right++
}
return res
};
leetcode 904 水果成篮
var totalFruit = function(fruits) {
let left = 0
let right = 0
let maxType = 2
let fruitType = 0
let fruitCnt = new Map()
let maxLen
while(right < fruits.length){
const f = fruits[right]
//如果是没加入的水果种类 种类数量增加
if(!fruitCnt.get(f)) {
fruitType+=1
}
//每一种水果的数量统计
fruitCnt.set(f,fruitCnt.has(f)?fruitCnt.get(f)+1:1)
// 当水果种类大于2 即不满足条件
while(fruitType > maxType){
const f2 = fruits[left]
//缩小窗口的过程中 如果有已有的水果 那么水果数量减一
if(fruitCnt.has(f2)){
fruitCnt.set(f2,fruitCnt.get(f2)-1)
}
//如果数量减少完了 那就让水果种类减1
if(fruitCnt.get(f2)===0){
fruitType-=1
}
left++
}
// 在窗口扩大时 计算最大数目
maxLen = Math.max(maxLen,(right - left)+1)
right++
}
return right
};