🧠 通过移动 right 和 left,始终维护一个 [left, right] 的连续区间,并根据题目条件不断扩大或缩小这个区间。

一句话结论:
👉 right 进窗(for 循环控制只增不减) left 出窗(只在 while (满足条件) 时移动只增不减)
注意窗口状态,遇到出现次数与不能重复联想哈希表 set(元素不能重复) 和 map(Key 不能重复,Value 可以重复)

⭐ 模板

  1. 初始化

    left = 0 (窗口的左边界)
    res = 初始值 (代表的全局最优解,求最大值可以刚开始设为 0,最小值设为无穷大)
    windows = {} (初始化窗口状态[Nt4])

  2. 窗口内数据处理(控制窗口右边界主动向右边扩张)

    for(int right = 0; right < nums.length; right++){
    //1.把元素添加进窗口
    window.add(nums[right]);

    //2.判断窗口是否合法 【出】如果不满足条件,left 被迫移动(范围变小)
    while(导致 left 必须右移的原因)
    {
    //3.把元素移出窗口
    window.remove(nums[left]);
    left++;
    }

    //4.判断定长还需要检查结果 (if)
    if(满足条件){

    }

    //更新 res[Nt2]
    }

  3. 返回值
    return res;

Notice

  1. 控制 left 的移动只可以使用 while 不可以使用 if
  1. res 更新位置

找最小 :在 for 里面 while 里面更新,要在 left++之前更新。

找最大 :在 for 里面 while 外面更新。

对于定长的滑动窗口:必须写在 “刚加进第 right 个元素” 之后,且 “还没踢掉第 left 个元素” 之前

  1. Map 的“尸体”清理 (Remove Key)

做计数类题目(如 904 水果成篮)时,最容易忘的一步。

现象:map.get(key) 已经减到 0 了,但 map.size() 还是没变。

后果:会导致 while (map.size() > 2) 变成死循环,或者逻辑完全错误。

口诀:“减到 0 必须 remove”。

1
2
3
4
map.put(outVal, map.get(outVal) - 1);
if (map.get(outVal) == 0) {
map.remove(outVal);
}
  1. Windows 初始化常见的四种状态

[是字符统计就用数组 int[];是数字统计就用 Map;只查重复就用 Set;只求数值和就用 int。]

  1. 数组 int[] (字符串题首选)
    适用场景:题目只包含字符(如 s 包含小写字母、ASCII 码)。

作用:统计字符出现的频率(次数)。

// 覆盖所有 ASCII 字符
int[] window = new int[128];
// 或者只覆盖小写字母 (需配合 char - ‘a’ 使用)
int[] window = new int[26];

Tips:数组的函数:

1
2
3
4
5
6
判断数组是否相同(元素,顺序,个数): Arrays.equals(arr1, arr2)
直接去字符串按照下标取字符: s.charAt(i)
转换下标: s.charAt(i) - 'a'
数组升序排序: Arrays.sort(arr)
把“字符串”拆解成新的“字符数组”: char[] chars = s.toCharArray();

  1. 哈希表 Map (通用型)
    适用场景:元素不是字符(比如数组里的数字),或者数字很大、很稀疏(如 [-10^9, 10^9])。

作用:统计元素出现的频率,或记录索引。

Map<Integer, Integer> window = new HashMap<>();

  1. 哈希集合 Set (判重型)
    适用场景:不需要统计“出现了几次”,只需要知道“有没有重复”。

作用:快速查找是否存在。最经典题目:无重复字符的最长子串。

Set window = new HashSet<>();

  1. 简单变量 int (数学型)
    适用场景:题目考察的是数值计算,如“子数组之和”。

作用:累加器,不需要存具体元素,只存总数。

int currentSum = 0; // 记录窗口内的和

  1. 求和+滑动窗口

要求数组里面不可以有负数,负数破坏了“越加越大”的规律,while 无法判断该缩还是该扩。

相关练习题

1. LeetCode 904. 水果成篮

题意: 你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

Tips:哈希表里的计数神器:

(哈希表里的计数神器:map.getOrDefault(key, defaultValue),key 是要查的元素,defa 是出现次数,但是他不会算上现在这一次,只查过去所以+1)

1
2
int inFruit = fruits[right];
map.put(inFruit, map.getOrDefault(inFruit, 0) + 1);

2. LeetCode 567. 字符串的排列

题意: 给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的 排列。如果是,返回 true ;否则,返回 false 。换句话说,s1 的排列之一是 s2 的 子串 。

Tips: [N-4.1]

3. LeetCode 76. 最小覆盖子串

题意: 给定两个字符串 s 和 t,长度分别是 m 和 n,返回 s 中的 最短窗口 子串,使得该子串包含 t 中的每一个字符(包括重复字符)。如果没有这样的子串,返回空字符串 “”。