Fork me on GitHub

滑动窗口总结

前言

滑动窗口是双指针的一种特例,可以称为左右指针,在任意时刻,只有一个指针运动,而另一个保持静止。滑动窗口路一般用于解决特定的序列中符合条件的连续的子序列的问题。
好处:时间复杂度 O(n^2) ---> O(n)

一、算法应用场景

关键词:

1.满足XXX条件(计算结果、出现次数、同时包含)
2.最长/最短/或最值
3.子串/子数组/子序列
最最最重要的提示点是:必须是连续的,否则不可以用滑动窗口

二、滑动窗口代码模板

说明:理论上你可以设计两端都开或者两端都闭的区间,但设计为左闭右开区间是最方便处理的。因为这样初始化 left = right = 0 时区间 [0, 0) 中没有元素,但只要让 right 向右移动(扩大)一位,区间 [0, 1) 就包含一个元素 0 了。如果你设置为两端都开的区间,那么让 right 向右移动一位后开区间 (0, 1) 仍然没有元素;如果你设置为两端都闭的区间,那么初始区间 [0, 0] 就包含了一个元素。这两种情况都会给边界处理带来不必要的麻烦。

给出了两个滑动窗口的模板,并且又给出了求最长/最短/固定时的模板,并不是说有五个模板,其实后三个模板是嵌套在前两个中的,即后三个模板需要关注的是内层while的条件(窗口固定情况下时也可用while,但是if即可满足)以及最终结果result的更新位置。

希望你写滑动窗口时能有三问:

1、什么时候应该扩大窗口?

2、什么时候应该缩小窗口?

3、什么时候应该更新答案?

滑动窗口 + 变量计数模板

class Solution {
    public int slidingWindow(int[] nums, int k) {
        //数组/字符串长度
        int n = nums.length;
        //双指针,表示当前遍历的区间[left, right),左闭右开
        int left = 0, right = 0;
        //定义变量统计 子数组/子区间 是否有效
        int sum = 0;
        //定义变量动态保存最大 求和/计数
        int res = 0;
        //右指针遍历到数组尾
        while (right < n) {
            //增加当前右指针对应的数值
            sum += nums[right];
            //增加窗口,移动右指针,实现右开效果
            right++//当在该区间内 sum 超出定义范围
            while (sum > k) {
                //先将左指针指向的数值减去
                sum -= nums[left];
                //缩小窗口
                left++;
            }
            //到 while 结束时,我们找到了一个符合题意要求的 子数组/子串
            res = Math.max(res, right - left);
        }
        return res;
    }
}

滑动窗口 + 哈希表存储模板

class Solution {
    public String slidingWindow(String s, String t) {
        //创建两个哈希表,分别记录 [窗口] 和 [需要的]
        Map<Character, Integer> window= new HashMap<>();
        Map<Character, Integer> need= new HashMap<>();
        //创建 [双指针] 和 [有效数量]
        int left = 0, right = 0;
        int valid = 0;
        //外层循环,供右指针遍历
        while(right < s.length()){
            //创建临时 c 字符,是移入 窗口 内的字符
            char c = s.charAt(right);
            //增大窗口
            right++;
            //进行窗口一系列逻辑更新
            ...
            /*** debug 输出的位置 ***/
            //System.out.println("window: [" + left + "," + right + ")");

            //判断左指针是否要右移即窗口收缩:有效数量足够满足条件
             /*  可能是规定的窗口大小超出了,可能是有效值数量达成了
             1.  while(valid == need.size())
             2.  while(right - left >= s1.length())      
             */           
            while(windows need shrink){
                // 创建 d 是要移除窗口的字符
                char d = s.charAt(left);
                //缩小窗口
                left++;
                //进行窗口一系列逻辑更新
                ...
            }
        }
    }
}                    

寻找最长模板(while为窗口不满足条件,结果result在外部更新)

初始化 left,right,window,result
while("右指针没有到结尾"){
    窗口扩大,加入right对应元素,更新当前window
    right++;(right右移,起到左闭右开的效果,[left,right))
    while("window不满足要求"){
        窗口缩小,移除left对应元素,left右移
    }
    更新最优结果result
}
返回result

寻找最短模板(while为窗口满足条件,结果result在内部更新)

初始化 left,right,window,result
while("右指针没有到结尾"){
    窗口扩大,加入right对应元素,更新当前window
    right++;(right右移,起到左闭右开的效果,[left,right))
    while("window满足要求"){
        更新最优结果result
        窗口缩小,移除left对应元素,left右移
    }
}
返回result

窗口大小固定模板

初始化 left,right,window,result
while("右指针没有到结尾"){
    窗口扩大,加入right对应元素,更新当前window
    right++;(right右移,起到左闭右开的效果,[left,right))
    if("window达到固定值(right-left==target_length)"){
        if(满足条件){
            处理结果;
        }
        窗口缩小,移除left对应元素,left右移
    }
}
返回result    

 

posted on 2023-03-31 10:52  Co3y  阅读(75)  评论(0编辑  收藏  举报

导航