什么是滑动窗口
滑动窗口的思想是:维护一个窗口,通过滑动窗口的方式在数组或字符串上移动。窗口通常由两个指针(左指针和右指针)定义,左指针指向窗口的起始位置,右指针指向窗口的结束位置。
- 初始化左右指针left和right,左右指针之间的内容就是窗口
- 定义一个变量result记录当前的滑动窗口的结果
- 定义一个变量bestResult记录当前滑动窗口下的最优结果
窗口的左边界和右边界永远只能向右移动,而不能向左移动,这是为了保证滑动窗口的时间复杂度是 O(n)。如果左右边界向左移动的话,这叫做"回溯",算法的时间复杂度就可能不止 O(n)
其实双指针和滑动窗口是有些许区别的。滑动窗口一句话就是右指针先出发,左指针视情况追赶右指针
模板代码
寻找最长滑窗
scss
初始化left,right,result,bestResult
while(右指针没有到结尾){
窗口扩大,加入right对应元素,更新当前result
while(result不满足要求){
窗口缩小,移除left对应元素,left右移
}
更新最优结果bestResult(注意是在外面更新)
right++
}
返回bestResult
寻找最短滑窗
scss
初始化left,right,result,bestResult
while(右指针没有到结尾){
窗口扩大,加入right对应元素,更新当前result
while(result不满足要求){
更新最优结果bestResult(在while内更新)
窗口缩小,移除left对应元素,left右移
}
right++
}
返回bestResult
相关题目
简-和为s的连续正数序列-剑指o57
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
思路:
滑动窗口只有 右边界向右移动(扩大窗口) 和 左边界向右移动(缩小窗口)
- 当窗口的和<target的时候,窗口的和需要增加,右边界向右移动
- 当窗口的和>target的时候,窗口的和需要减少,左边界向右移动
- 当窗口的和=target的时候,记录当前结果[i,j),接下来找i+1开头的序列,窗口的左边界向右移动
javascript
/**
* @param {number} target
* @return {number[][]}
*/
var findContinuousSequence = function(target) {
// 这个窗口是一个左闭右开的区间
let i=1//左边界
let j=1//右边界
let sum=0 //滑动窗口的数字和
const res=[]
// 只循环到目标值的一半,超过一半的话,加起来会超过target
while(i<=Math.floor(target/2)){
// 右边界移动
if(sum<target){
sum+=j
j++
}
// 左边界移动
else if(sum>target){
sum-=i
i++
}
// 记录结果
else{
let arr=[]
for(let k=i;k<j;k++){
arr.push(k)
}
res.push(arr)
// 左边界移动
sum-=i
i++
}
}
return res
};
中-长度最小的子数组-209
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
思路:本质上是找最小滑窗
- 定义左右指针,指向滑动窗口的开始和结束位置
- 从右移动右指针,计算窗口内数之和sum
- 如果sum>=target,就更新子数组最小长度(right-left+1)
- 向右移动left,同时sum-left的值,直到sum<target
scss
var minSubArrayLen = function (target, nums) {
let left = right = sum = bestResult = 0;
//右指针到最右边结束循环
while(right<nums.lenght){
sum=sum+nums[right]//记录当前滑动窗口的值
// 这里记住是要用while,而不是if!!
// 满足条件
while(sum>=target){
// 如果当前的最好的结果大于左右指针之间的长度,更新最优结果
if(right-left+1<bestResult||bestResult==0){
bestResult = right - left + 1;
}
// 将左指针向右移,并且左指针的值从当前结果里面减掉
sum -= nums[left];
left++;
}
right++; // 向右滑动窗口
}
return bestResult;
}
中-无重复字符的最长字符串-3
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
思路:本质是找最长滑窗
- 创建一个set
- 两个指针(left和right)
- 如果set里面没有s[right],说明目前为止还没有重复的字符,把s[right]添加到set里,然后更新最大不重复字符的数量
- 如果set里有s[right],则从set里开始删除s[left],并且递增,再检查set是否有s[right],循环直到没有s[right]为止
- 重复步骤3,4,直到遍历完整个字符串
使用set
scss
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
if(s.length === 0){
return 0
}
const set = new Set()
let left=0,right=0,maxLength=0
while(right<s.length){
if(!set.has(s[right])){
set.add(s[right])
right++
// 更新最大长度
maxLength=Math.max(maxLength,set.size)
}else{
// 存在重复值,则删除set中s[i]以及之前的元素
while(set.has(s[right])){
set.delete(s[left])
left++
}
set.add(s[right])
right++
}
}
return maxLength
};
不使用set
这种写法更容易理解,但是需要考虑重复的情况
scss
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
let left=right=len=maxLen=0
let arr=[]//记录当前滑动窗口包含的字符
//当右指针指向字符串最右边时停止循环
while(right<s.length){
//如果窗口中的字符串包含了右指针指向的元素
if(arr.includes(s[right])){
// 当arr中包含右指针指向的字符,删除该元素
while(arr.includes(s[right])){
arr.shift()//删除
len--//更新当前的长度
left++//左指针右移动
}
//循环结束后,arr中不包含右指针指向的元素
arr.push(s[right])
len++
right++
}else{
arr.push(s[right])
len++
//如果当前长度大于最大长度
if(len>maxLen){
maxLen=len
}
right++
}
}
return maxLen
};
中-水果成篮-904
题目简述:有两个篮子,每个篮子只能放一种水果,找出储存最多水果的方案,本质是找最大滑窗
思路:
Leetcode刷题 904. 水果成篮Fruit Into Baskets_哔哩哔哩_bilibili
- 定义两个变量,right为窗口的右边界,left为窗口的左边界
- 使用map储存每个水果最后出现的位置
- 窗口中只能放两个水果,当出现第三种水果时,将第一种水果去除出去,即移动j的坐标
scss
/**
* @param {number[]} fruits
* @return {number}
*/
var totalFruit = function(fruits) {
const map = new Map()
let max = 1
let left=0
for(let right=0;right<tree.length;right++){
// map用来存储每个水果出现的最后位置
map.set((tree[i],i))
// 不满足条件(超过两种水果)
if(map.size>2){
// 初始值(给一个最大的值)
let minIndex = tree.length-1
// 找到map中比较小的坐标
for(const [fruit,index] of map){
if(index<minIndex){
minIndex = index
}
}
// 删除小坐标的水果
map.delete(tree[minIndex])
// j往后移动
left=minIndex+1
}
// 更新最大值
max = Math.max(max,right-left+1)
}
return max
};