滑动窗口刷题总结

1. 滑动窗口基本写法

滑动窗口需要确定三个条件

  • 窗口内是什么
  • 如何移动窗口的起始位置
  • 如何移动窗口的结束位置

例题209 长度最小的子数组
本题窗口内时元素的和,当元素和大于目标值就需要缩小窗口范围尝试搜索更优的解
注意:这里要先对当前最优结果进行保存,再去尝试找更优的解

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int sum = 0;
        int i = 0,res = Integer.MAX_VALUE;
        //以尾指针作为循环变量
        for(int j = 0;j < nums.length;j++){
            sum += nums[j];
            //如果sum之和大于了target,就要缩小窗口的大小,同时与之前保存的长度进行比较,确认保存最小的长度
            while(sum >= target){
                //记录子数组的长度
                // int subLength = j - i + 1;
                // res = res > subLength ? subLength : res;
                //可以直接使用内置函数
                res = Math.min(res,j-i+1);
                //缩小窗口大小
                sum -= nums[i++];
            }
        }
        return res == Integer.MAX_VALUE ? 0 : res;
    }
}

2.边长滑动窗口与哈希Map结合

数组类型
904 水果成篮
本题的窗口内保存的是水果的种类数,只要超过两种就需要缩小窗口大小,即移动左边界
注意:这里需要先拿出水果,再去保存最优值。因为水果种类数等于2时,下一个要遍历的值可能还是当前篮子种类型的水果,不能直接开始拿水果的操作。

class Solution {
    public int totalFruit(int[] fruits) {
        //定义变量保存结果
        int res = 0;
        //用来保存水果的数量
        Map<Integer,Integer> count = new HashMap<>();
        //i为左指针,j为右指针
        int i = 0;
        for(int j = 0;j < fruits.length;j++){
            //开始装水果
            count.put(fruits[j],count.getOrDefault(fruits[j],0)+1);
            //水果种类超过两种时
            while(count.size() > 2){
                //拿出水果
                count.put(fruits[i],count.getOrDefault(fruits[i],0)-1);
                //如果水果个数为0,就移除该类型的水果
                if(count.get(fruits[i]) == 0){
                    count.remove(fruits[i]);
                }
                //左指针向后移
                i++;
            }
            //尽可能让滑动窗口大,这个时候说明某个类型的水果很多
            res = Math.max(res,j-i+1);
        }
        return res;
    }
}

面试题 17.18. 最短超串
本题与下文将要介绍的字符串类型的两题类似,唯一不同的就是返回值,这里需要更加注意边界条件的判断

class Solution {
    public int[] shortestSeq(int[] big, int[] small) {
         //如果big的长度小于small的长度,那就不可能满足条件
        // if(big.length < small.length) return new int[]{};
        //定义一个map存储small需要的字符种类和对应的数量
        Map<Integer,Integer> cnt = new HashMap<>();
        //遍历small数组保存map的值
        for(int i = 0;i < small.length;i++){
            cnt.put(small[i],cnt.getOrDefault(small[i],0)+1);
        }
        //定义除了滑动窗口外还需要匹配的数字
        int need = small.length;
        int n = big.length;
        //定义一个较大值
        int minLen = n + 1;
        //定义滑动窗口左右指针
        int left = 0,right;
        int start = 0,end = -1;
        //开始滑动窗口啦
        for(right = 0;right < n;right++){
            //扩大右边界,同时修改map和还需要的字符
            int ele = big[right];
            if(cnt.containsKey(ele)){
                if(cnt.get(ele) > 0){//对该数字还有需求
                    need--;
                }
                cnt.put(ele,cnt.get(ele)-1);
            }
            //循环缩小左边界,需要在need为0,即对所有数字都没有需求的时候开始缩小左边界,获取最小长度
            while(need == 0){
                ele = big[left];
                //保存当前最优值
                if(right - left + 1 < minLen){
                    minLen = right - left + 1;
                    start = left;
                    end = right;
                }
                //缩小边界尝试找更短
                if(cnt.containsKey(ele)){
                    if(cnt.get(ele) >= 0){//还有需求,
                        need++;
                    }
                    cnt.put(ele,cnt.get(ele)+1);
                }
                left++;
            }
        }

        return start <= end ? new int[]{start,end} : new int[]{};//注意边界条件的判断
    }
}

字符串类型
只要字符串一种包含字符串二的所有字符,然后长度最短就可以,字符串二不一定是字符串一的子串
76 最小覆盖子串
剑指 Offer II 017. 含有所有字符的最短字符串
这里窗口内保存的是 是否s已经覆盖t,如果已经覆盖了再收缩边界。
注意:t字符串可能会重复,所以哈希map不仅要保存当前需要的字符种类,还要保存当前需要字符的数量。如果哈希map值为负数,说明已经不需要该字符了。

class Solution {
    public String minWindow(String s, String t) {
        //如果s的长度小于t的长度,那就不可能有覆盖字串
        if(s.length() < t.length()) return "";
        //定义一个map存储t需要的字符种类和对应的数量
        Map<Character,Integer> cnt = new HashMap<>();
        //遍历t字符串保存map的值
        for(int i = 0;i < t.length();i++){
            cnt.put(t.charAt(i),cnt.getOrDefault(t.charAt(i),0)+1);
        }
        //定义除了滑动窗口外还需要匹配的字符的数目
        int need = t.length();
        int n = s.length();
        //定义一个较大值
        int minLen = n + 1;
        //定义滑动窗口左右指针
        int left = 0,right;
        int start = 0,end = -1;
        //开始滑动窗口啦
        for(right = 0;right < n;right++){
            //扩大右边界,同时修改map和还需要的字符
            char ch = s.charAt(right);
            if(cnt.containsKey(ch)){
                if(cnt.get(ch) > 0){//对该字符还有需求
                    need--;
                }
                cnt.put(ch,cnt.get(ch)-1);
            }
            //循环缩小左边界,需要在need为0,即对所有字符都没有需求的时候开始缩小左边界,获取最小长度
            while(need == 0){
                ch = s.charAt(left);
                //保存当前最优值
                if(right - left + 1 < minLen){
                    minLen = right - left + 1;
                    start = left;
                    end = right;
                }
                //缩小边界尝试找更短
                if(cnt.containsKey(ch)){
                    if(cnt.get(ch) >= 0){//还有需求,
                        need++;
                    }
                    cnt.put(ch,cnt.get(ch)+1);
                }
                left++;
            }
        }
        return s.substring(start,end+1);
    }
}

3.定长的滑动窗口------异位词

判断第一个字符串的排列之一是第二个字符串的子串
567 字符串的排列
剑指 Offer II 014. 字符串中的变位词
维护一个长度为s1.length()的窗口,如果need等于0,说明当前窗口中的元素正好与s1相同

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        if(s2.length() < s1.length()) return false;
        //定义一个map存储t需要的字符种类和对应的数量
        Map<Character,Integer> cnt = new HashMap<>();
        for(int i = 0;i < s1.length();i++){
            cnt.put(s1.charAt(i),cnt.getOrDefault(s1.charAt(i),0)+1);
        }
        //定义除了滑动窗口外还需要匹配的字符的数目
        int need = s1.length();
        int n = s2.length();
        //定义滑动窗口左右指针
        int left,right;
        //开始滑动窗口啦,这里的滑动窗口是一个定长的滑动窗口,其实就是每次判断一个不包含左边界,只包含右边界的窗口内的值是否和s1的值相等
        for(right = 0;right < n;right++){
            char ch = s2.charAt(right);
            if(cnt.containsKey(ch)){
                if(cnt.get(ch) > 0){//对该字符还有需求
                    need--;
                }
                cnt.put(ch,cnt.get(ch)-1);
            }
            left = right - s1.length();
            if(left >= 0){
                ch = s2.charAt(left);
                if(cnt.containsKey(ch)){
                    if(cnt.get(ch) >= 0){
                        need++;
                    }
                    cnt.put(ch,cnt.get(ch)+1);
                }  
            }
             if(need == 0) return true;
        }
        return false;
    }
}

4.定长滑动窗口------异位词的位置

438. 找到字符串中所有字母异位词
剑指 Offer II 015. 字符串中的所有变位词
维护一个长度为s1.length()的窗口,如果need等于0,说明当前窗口中的元素正好与s1相同,此时返回left+1,因为窗口是不包含左端点的。

class Solution {
    public List<Integer> findAnagrams(String s, String t) {
         List<Integer> res = new ArrayList<>();
          //如果s的长度小于t的长度,那就不可能有覆盖字串
        //if(s.length() < t.length()) return res;
        //定义一个map存储t需要的字符种类和对应的数量
        Map<Character,Integer> cnt = new HashMap<>();
        //遍历t字符串保存map的值
        for(int i = 0;i < t.length();i++){
            cnt.put(t.charAt(i),cnt.getOrDefault(t.charAt(i),0)+1);
        }
        //定义除了滑动窗口外还需要匹配的字符的数目
        int need = t.length();
        int n = s.length();
        //定义滑动窗口左右指针
        int left = 0,right;
        //开始滑动窗口啦
        for(right = 0;right < n;right++){
            //扩大右边界,同时修改map和还需要的字符
            char ch = s.charAt(right);
            if(cnt.containsKey(ch)){
                if(cnt.get(ch) > 0){//对该字符还有需求
                    need--;
                }
                cnt.put(ch,cnt.get(ch)-1);
            }

           left = right - t.length();
           if(left >= 0){
                ch = s.charAt(left);
                if(cnt.containsKey(ch)){
                    if(cnt.get(ch) >= 0){
                        need++;
                    }
                    cnt.put(ch,cnt.get(ch)+1);
                }
            }
            if(need == 0) res.add(left+1);
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值