前置

定长

  • 定长处理比较容易,不用关心窗口的大小,只用针对满足特定数组的条件进行处理判断。
  • 主要的思路如下:
  • 窗口右端点在 i 时,由于窗口长度为 k,所以窗口左端点为 i−k+1。
    • 三步:入-更新-出。
    • :下标为 i 的元素进入窗口,更新相关统计量。如果窗口左端点 i−k+1<0,则尚未形成第一个窗口,重复第一步。
    • 更新:更新答案。
    • :下标为 i−k+1 的元素离开窗口,更新相关统计量,为下一个循环做准备。

不定长

  • 不定长滑动窗口主要分为三类:求最长子数组,求最短子数组,求子数组个数。

  • 求最长子数组和最短子数组,还是入——更新——出,三步走。不过会略有不同。

    • 更新:下标为 i 的元素进入。如果满足条件,加入,重复,继续扩大,直到遇到不合适情况。
    • 出:下标为 left 的元素离开窗口,left++ ,记录此时带窗口大小 i-left+1,为下一个循环做准备。
  • 求子数组个数

    • 越短越合法 一般要写 ans += right - left + 1。内层循环结束后,[left, right] 这个子数组是满足题目要求的。由于子数组越短,越能满足题目要求,所以除了 [left, right],还有 [left+1, right],[left+2, right],…,[right, right] 都是满足要求的。也就是说,当右端点固定在 right 时,左端点在 left, left+1, left+2,…, right 的所有子数组都是满足要求的,这一共有 right−left+1 个。
    • 越长越合法 一般要写 ans += left。内层循环结束后,[left, right] 这个子数组是不满足题目要求的,但在退出循环之前的最后一轮循环,[left−1, right] 是满足题目要求的。由于子数组越长,越能满足题目要求,所以除了 [left−1, right],还有 [left−2, right],[left−3, right],…,[0, right] 都是满足要求的。也就是说,当右端点固定在 right 时,左端点在 0,1,2,…, left−1 的所有子数组都是满足要求的,这一共有 left 个。我们关注的是 left−1 的合法性,而不是 left。
  • 恰好型滑动

维度(至多≤)(至少≥)
函数目标统计和≤x 的子数组数统计和≥x 的子数组数
相减逻辑用 “相邻小范围” 抠除(k 和 k-1)用 “相邻大范围” 抠除(k 和 k+1)
窗口收缩条件sum > x(保证窗口内 sum≤x)sum ≥x(收缩到 sum<x)
计数方式ans += r-l+1(窗口长度)ans += l(左边界位置)
核心逻辑正向:直接统计≤x反向:统计≥x

记录

定长处理

  • 力扣:1456. 定长子串中元音的最大数目

    • 套用之前所给的解题思路即可,按照入—更新—出,标准三步就行。
  • 力扣:643. 子数组最大平均数 I

    • 同样思路,只不过从子数组中的元音字母个数改成了平均数。
  • 力扣: 1343. 大小为 K 且平均值大于等于阈值的子数组数目

    • 本题只是对643 题求取的结果进行了筛选
  • 力扣: 2379. 得到 K 个黑块的最少涂色次数

    • 与 1456 相似,遇到 w,进入窗口,然后更新,出窗口,最后返回最小值。
  • 力扣:2841. 几乎唯一子数组的最大和

    • 与 1343 相似,从大于等于阈值的数变成最大而已。
  • 力扣:2461. 长度为 K 子数组中的最大和

    • 变式,要求我们子数组中的元素不相同,且求取最大值。题643 加上附加条件。之前我们一般使用变量来存储信息,本体改用 map 存储。我们对 key 的 value 进行入——更新——出的操作。(注意 map 的操作方法)
  • 力扣:1423. 可获得的最大点数

    • 题643变式,逆向思维。我们求拿去之后剩下子数组的最小和。再用最大和减去即可。
  • 力扣: 3679. 使库存平衡的最少丢弃次数

    • 题 2461 变式,加上阅读理解干扰。
    • 如果我们仿照 2461 题,大概的结果如下,但结果是不对的。
    • 原因是:>m 的c在未来要离开窗口,但由于已经丢弃,不能在后续离开窗口时修改,否则会破坏数组索引连续性,导致窗口边界无法准确定位。因此改用“虚拟标记”实现软删除:被丢弃的元素仅标记为特定值(如0),后续统计频率或处理窗口滑动时,只要碰到该标记就判定为无效元素,不纳入频率计算。
class Solution {
    public int minArrivalsToDiscard(int[] arrivals, int w, int m) {
        int ans = 0
        Map<Integer,Integer> map = new HashMap<>();
        int n = arrivals.length;
        for(int i = 1 ;i < n;i++){
            int left = i-w + 1;
            map.merge(arrivals[i],1,Integer::sum);
            if(i - w + 1< 0){
                continue;
            }
            if(map.getOrDefault(num, 0) > m){
                idx++;
                //这里有问题
                int c = map.getOrDefault(num, 0);
                if(c > 1){
                    map.put(num,c-1);
                }else{
                    map.remove(num);
                }
            }
            int out = arrivals[left];
            int c = map.getOrDefault(out, 0);
            if(c > m) {
                idx--;
            }
            if(c > 1){
                map.put(out,c-1);
            }else{
                map.remove(out);
            }
        }
        return ans;
    }
}
//正确思路
class Solution {
    public int minArrivalsToDiscard(int[] arrivals, int w, int m) {
        Map<Integer, Integer> cnt = new HashMap<>(); 
        int ans = 0;
        for (int i = 0; i < arrivals.length; i++) {
            int x = arrivals[i];
            // x 进入窗口
            int c = cnt.getOrDefault(x, 0);
            if (c == m) { // x 的个数已达上限
                // 注意 x 在未来要离开窗口,但由于已经丢弃,不能在离开窗口时修改 cnt
                // 这里直接置为 0,未来离开窗口就是 cnt[0]--,不影响答案
                arrivals[i] = 0; // 丢弃 arrivals[i]
                ans++;
            } else {
                cnt.put(x, c + 1);
            }

            // 左端点元素离开窗口,为下一个循环做准备
            int left = i + 1 - w;
            if (left >= 0) {
                cnt.merge(arrivals[left], -1, Integer::sum); // cnt[arrivals[left]]--
            }
        }
        return ans;
    }
}
  • 力扣: 1052. 爱生气的书店老板

    • 阅读理解+题 643 变式。
    • 我们先求出在不生气是时间段的顾客数量,再把那时的顾客数量变为零,那么问题就转化为求一个窗口内最大数的问题了。有点和 3679 相似,本事给的数组也是可以利用的条件,可以用它来简化逻辑。
  • 力扣: 3439. 重新安排会议得到最多空余时间 I

    • 抽象思维+题 643 变式+阅读理解
    • 用一个数组记录其中有几个空闲时间段,求合并这些空闲时间段的最大数,又因为题目要求,这些空闲空闲时间段必然是连续合并,所以可以看成滑动窗口。
    • 观察题目。题目就转化成了,有n+1 个空余时间段(空闲时间段可以为 0),合并其中连续 k+1 个空余时间段,得到的最大长度是多少

不定长

求最长

  • 力扣:3. 无重复字符的最长子串
    • 有点类似题 2461 变式,都是要求不重复的,不过字符用数组记录就行了。余下的按照正常的不定长处理方法就可以了。
class Solution {
    public int lengthOfLongestSubstring(String s) {
        char[] S = s.toCharArray();
        int ans = 0;
        int left = 0;
        int[] cnt = new int[128];
        for(int i = 0; i < S.length;i++){
            char c = S[i];
            cnt[c]++;
            while(cnt[c] > 1){
                cnt[S[left]]--;
                left++;
            }
            ans = Math.max(ans,i-left+1);
        }
        return ans;
    }
}
class Solution {
    public int maximumBeauty(int[] nums, int k) {
        Arrays.sort(nums);
        int ans = 0;
        int left = 0;
        for (int i = 0; i < nums.length; i++) {
            while (nums[i] - nums[left] > 2*k) {
                left++;
            }
            ans = Math.max(ans,i-left+1);
        }
        return ans;
    }
}

求最短

求子数组个数

class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {
        if(k<=1){
            return 0;
        }
        int ans =0;
        int left= 0;
        int cnt =1;
        for(int i =0; i<nums.length;i++){
            cnt *= nums[i];
            while(cnt >=k ){
              cnt /= nums[left];
              left++;
            }
            ans +=i-left+1;
        }
        return ans;
    }
}
  int[] cnt = new int[2];
  cnt[nums[left] & 1]--;
class Solution {
    public int numberOfSubstrings(String s) {
        char[] S = s.toCharArray();
        int ans = 0;
        int left = 0;
        Map<Character, Integer> map = new HashMap<>();
        map.put('a', 0);
        map.put('b', 0);
        map.put('c', 0);
        for (int i = 0; i < S.length; i++) {
            map.merge(S[i], 1, Integer::sum);
            while (map.get('a') > 0 && map.get('b') > 0 && map.get('c') > 0) {
                map.put(S[left], map.get(S[left]) - 1);
                left++;
            }
             ans += left;  
        }
        return ans;
    }
}