滑动窗口 = 面试 & 刷题里性价比最高的算法之一
什么时候用 → 本质 → 固定窗口 → 变长窗口 → 判断口诀
一、一句话先建立直觉(最重要)
当你在一个“连续区间”上,反复计算某个指标,且每次只移动一点点时,就该想到滑动窗口。
关键词只有三个:
连续 + 区间 + 重复计算二、什么时候「一定」要用滑动窗口?
你看到题目里出现这些描述,第一反应就该是它👇
✅ 典型触发词
| 题目描述 | 为什么 |
|---|---|
| 连续子数组 / 子串 | 区间连续 |
| 长度固定 k | 固定窗口 |
| 至多 / 至少 | 窗口可变 |
| 最大 / 最小 / 最优 | 比较窗口结果 |
| 每次移动一步 | 可复用上一次结果 |
🚫 什么时候不适合
- 不要求连续(子序列、组合)
- 窗口跳跃、随机访问
- 区间大小完全不确定且不可单调调整
三、滑动窗口的「本质」
先想一个暴力解法
枚举所有区间 → 每个区间重新算一遍
O(n²) 或 O(n³)滑动窗口在干嘛?
我不重新算
我只:
- 加一个新元素
- 删一个旧元素
O(n)这就是它的全部灵魂。
四、第一种:固定长度滑动窗口(最好学)
典型问题
长度为 k 的子数组,最大和是多少?
暴力思路(不要写)
for(i)for(j=i..i+k-1)sum+=nums[j];滑动窗口写法(模板)
intwindowSum=0;// 初始化第一个窗口for(inti=0;i<k;i++){windowSum+=nums[i];}intmax=windowSum;// 开始滑动for(inti=k;i<n;i++){windowSum+=nums[i];// 加右边windowSum-=nums[i-k];// 减左边max=Math.max(max,windowSum);}记忆口诀(重要)
右边进,左边出,窗口长度始终不变
五、第二种:可变长度滑动窗口(面试最爱)
典型问题
最短子数组,使得和 ≥ target
思路(非常重要)
- 右指针扩张(满足条件)
- 左指针收缩(尝试变优)
- 条件破坏 → 停止收缩
模板
intleft=0;intsum=0;intminLen=Integer.MAX_VALUE;for(intright=0;right<n;right++){sum+=nums[right];while(sum>=target){minLen=Math.min(minLen,right-left+1);sum-=nums[left];left++;}}口诀(必须背)
右扩张满足条件,左收缩寻找最优
六、什么时候用「固定」?什么时候用「可变」?
| 情况 | 用法 |
|---|---|
| 长度固定(k) | 固定窗口 |
| 长度不固定 | 可变窗口 |
| 要最大 / 最小 | 两者都可能 |
| 至多 / 至少 | 可变窗口 |
七、你刚做的股票题 = 固定窗口的变形
你那题本质是:
长度为 k 的区间,最大“增量”是多少
✔ 连续
✔ 长度固定
✔ 比最大值
👉100% 固定滑动窗口
八、滑动窗口“识别口诀”(送你)
看到题目,问自己 3 个问题:
1. 要不要连续? 2. 窗口会不会整体右移? 3. 能不能用“加一个、减一个”更新结果?三问全是「是」👇
直接滑动窗口,不要想 DP
九、给你一个“肌肉记忆版”模板
固定窗口模板(背下来)
// initfor(inti=0;i<k;i++){window+=arr[i];}ans=window;// slidefor(inti=k;i<n;i++){window+=arr[i];window-=arr[i-k];ans=Math.max(ans,window);}可变窗口模板(背下来)
left=0;for(right=0..n-1){add(arr[right]);while(invalid()){remove(arr[left++]);}updateAnswer();}十、最后一句话(很重要)
滑动窗口不是一种技巧,而是一种“拒绝重复计算”的思维方式
题 1(固定窗口)
LeetCode 643:子数组最大平均数 I
题意(极简版)
给你一个数组,找长度为 k 的连续子数组,使它们的和最大。
错误直觉(不要做)
枚举所有区间 → 每个重新算和 → O(n²)正确直觉(看到这句话就反射)
“长度固定 + 连续 + 最大” → 固定滑动窗口
思维流程(一定要记)
1. 先算第一个窗口 2. 右边进一个 3. 左边出一个 4. 更新答案Java 模板(背)
intwindowSum=0;// 1. 初始化窗口for(inti=0;i<k;i++){windowSum+=nums[i];}intmaxSum=windowSum;// 2. 开始滑动for(inti=k;i<nums.length;i++){windowSum+=nums[i];// 右进windowSum-=nums[i-k];// 左出maxSum=Math.max(maxSum,windowSum);}肌肉记忆点
固定窗口 = for + 加一个 + 减一个
题 2(可变窗口 · 至少型)
LeetCode 209:长度最小的子数组
题意
找一个最短的连续子数组,使得和 ≥ target
关键转变(非常重要)
这里窗口长度不固定
但有一个非常关键的特性:
窗口越大,和只会越大(正数数组)
这叫单调性
一看到这个,就能用滑动窗口
思维流程(牢记)
右指针:负责扩张 → 直到满足条件 左指针:负责收缩 → 尝试变短Java 模板(必背)
intleft=0;intsum=0;intminLen=Integer.MAX_VALUE;for(intright=0;right<nums.length;right++){sum+=nums[right];// 扩张窗口while(sum>=target){minLen=Math.min(minLen,right-left+1);sum-=nums[left];// 收缩窗口left++;}}3 肌肉记忆点
“满足条件 → 左边能不能再缩?”
题 3(可变窗口 · 计数型)
LeetCode 3:无重复字符的最长子串
题意
找一个最长的子串,里面没有重复字符
为什么还是滑动窗口?
- 连续子串
- 条件:窗口内字符不能重复
- 一旦重复 → 左边必须动
这里的“窗口状态”是什么?
不是 sum,而是:
窗口内字符的出现次数用HashSet或Map
Java 模板(经典中的经典)
Set<Character>set=newHashSet<>();intleft=0;intmaxLen=0;for(intright=0;right<s.length();right++){charc=s.charAt(right);// 出现重复 → 收缩左边while(set.contains(c)){set.remove(s.charAt(left));left++;}set.add(c);maxLen=Math.max(maxLen,right-left+1);}肌肉记忆点
“不合法 → 一直动左边,直到合法”
三道题 = 三种滑动窗口“人格”
| 题 | 类型 | 你该想到什么 |
|---|---|---|
| 最大和 | 固定窗口 | 加一个,减一个 |
| 最短满足 | 可变窗口(至少) | 右扩张,左收缩 |
| 无重复 | 可变窗口(约束) | 违规就缩 |
最终「识别口诀」(你以后靠它)
看到题目,心里过这 4 句:
1. 连续吗? 2. 能不能整体右移? 3. 能不能用“加一个、减一个”维护状态? 4. 是否存在“满足条件 / 不满足条件”的边界?✔ 全是 →滑动窗口
给你一个终极总结(很重要)
滑动窗口不是算法,是“不重复计算”的习惯