下面给你一套系统化的 Java 滑动窗口算法练习计划,从基础到进阶,包含经典题型、模板代码、思路分析和 Java 实现。
滑动窗口主要分为两大类:
- 固定窗口大小(Fixed Size Sliding Window)
- 可变窗口大小(Variable Size / Dynamic Sliding Window)
推荐练习顺序(由易到难)
| 难度 | 题号 & 标题 | 窗口类型 | 核心技巧 | 推荐指数 |
|---|---|---|---|---|
| 简单 | 643. Maximum Average Subarray I | 固定 | 前缀和 / 滑动求和 | ★★★★ |
| 简单 | 209. Minimum Size Subarray Sum | 可变 | 最小长度 ≥ target | ★★★★★ |
| 中等 | 3. Longest Substring Without Repeating Characters | 可变 | HashSet / HashMap 记录字符 | ★★★★★ |
| 中等 | 438. Find All Anagrams in a String | 固定 | 字符频率计数(滑动匹配) | ★★★★ |
| 中等 | 567. Permutation in String | 固定 | 滑动窗口 + 频率数组 | ★★★★ |
| 中等 | 76. Minimum Window Substring | 可变 | 最小覆盖子串(need & window) | ★★★★★ |
| 中等 | 1004. Max Consecutive Ones III | 可变 | 最多允许 k 个 0 | ★★★★ |
| 困难 | 239. Sliding Window Maximum | 固定 | 单调队列(deque) | ★★★★★ |
| 困难 | 424. Longest Repeating Character Replacement | 可变 | 最多替换 k 个字符 | ★★★★ |
| 困难 | 295. Find Median from Data Stream (进阶变形) | 动态中位数 | 两个堆(非纯滑动,但相关) | ★★★ |
滑动窗口通用模板(Java)
1. 固定窗口大小模板
class Solution {
public int fixedWindow(int[] nums, int k) {
int n = nums.length;
if (n < k) return 0; // 或其他无效值
int left = 0, right = 0;
int sum = 0; // 或其他状态变量(如计数器)
int maxSum = Integer.MIN_VALUE;
while (right < n) {
// 加入右边界元素
sum += nums[right];
right++;
// 当窗口达到固定大小 k
if (right - left == k) {
// 更新答案
maxSum = Math.max(maxSum, sum);
// 移除左边界元素,窗口向前滑动
sum -= nums[left];
left++;
}
}
return maxSum;
}
}
2. 可变窗口大小模板(最常用)
class Solution {
public int variableWindow(int[] nums, int target) {
int left = 0;
int sum = 0; // 当前窗口和 / 计数等
int minLen = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
// 加入右边界
sum += nums[right];
// 窗口收缩条件(while 非常关键)
while (sum >= target && left <= right) {
// 更新答案
minLen = Math.min(minLen, right - left + 1);
// 移除左边界
sum -= nums[left];
left++;
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
}
经典题目 Java 代码 + 思路精讲
1. 209. 长度最小的子数组(可变窗口,最经典入门题)
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
int left = 0;
int sum = 0;
int minLen = Integer.MAX_VALUE;
for (int right = 0; right < n; right++) {
sum += nums[right];
while (sum >= target && left <= right) {
minLen = Math.min(minLen, right - left + 1);
sum -= nums[left++];
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
}
2. 3. 无重复字符的最长子串(可变窗口 + HashMap/数组)
class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int left = 0;
int maxLen = 0;
int[] window = new int[128]; // ASCII 字符
for (int right = 0; right < n; right++) {
char c = s.charAt(right);
window[c]++;
while (window[c] > 1) {
char d = s.charAt(left);
window[d]--;
left++;
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
}
3. 76. 最小覆盖子串(最经典可变窗口高级题)
class Solution {
public String minWindow(String s, String t) {
if (s == null || t == null || s.length() < t.length()) return "";
int[] need = new int[128];
int[] window = new int[128];
// 记录 t 中每个字符需要的次数
for (char c : t.toCharArray()) {
need[c]++;
}
int left = 0, right = 0;
int valid = 0; // 窗口中已满足条件的字符数
int start = 0, len = Integer.MAX_VALUE;
while (right < s.length()) {
char c = s.charAt(right);
right++;
if (need[c] > 0) {
window[c]++;
if (window[c] == need[c]) valid++;
}
// 收缩窗口
while (valid == t.length()) {
if (right - left < len) {
start = left;
len = right - left;
}
char d = s.charAt(left);
left++;
if (need[d] > 0) {
if (window[d] == need[d]) valid--;
window[d]--;
}
}
}
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
}
}
4. 239. 滑动窗口最大值(固定窗口 + 单调队列)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
if (n == 0) return new int[0];
Deque<Integer> deque = new ArrayDeque<>(); // 存索引,单调递减
int[] result = new int[n - k + 1];
for (int i = 0; i < n; i++) {
// 移除窗口外的元素
while (!deque.isEmpty() && deque.peekFirst() <= i - k) {
deque.pollFirst();
}
// 保持单调递减(队首是最大值)
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
// 窗口形成后记录最大值
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
}
练习建议
- 先掌握 固定窗口(643、1004)→ 熟悉窗口滑动的基本节奏
- 再练 可变窗口 + 计数(3、209、424、567、438)
- 挑战 最小覆盖子串(76)→ 这是可变窗口的集大成者
- 最后攻克 单调队列(239)→ 固定窗口的进阶玩法
- 每天 2–3 题,写完后对比官方题解的优化点
如果你想针对某一道题要详细的多种解法对比(HashMap vs 数组 vs BitSet 等)、边界case分析,或者想加几道面试高频变形题(如水果成篮、至多k种字符等),告诉我,我可以继续展开。祝你刷题顺利!