0
点赞
收藏
分享

微信扫一扫

刷题笔记(三)--数组类型:滑动窗口

cwq聖泉寒江2020 2022-04-15 阅读 28

目录

系列文章目录

刷题笔记(一)–数组类型:二分法
刷题笔记(二)–数组类型:双指针法

题录

209.长度最小的子数组

链接:209.长度最小的子数组
对应题目截图如下:
在这里插入图片描述

这个题目有两种解法,第一种就是暴力解法,我直接遍历整个数组,然后得出每一个下标对应的大于target的窗口长度是多少,然后取最小的那一个。

class Solution {
    public static int minSubArrayLen(int target, int[] nums) {
        int left = 0;//定义左指针下标
        int right = 0;//定义右指针下标
        int lengthRs = target;//定义最小的长度
        boolean flagMe = false;
        for (left = 0; left < nums.length; left++) {
            int res = nums[left];//记录当前的和
            int lengthNow = 1;//定义当前的长度
            right = left + 1;//右指针不能和左指针同一个下标
            //如果说当前和小于target或者说右指针越界了就不能循环了
            while(res < target && right < nums.length){
                res += nums[right];
                lengthNow++;
                right++;
            }
            boolean flag = false;
            if(res >= target){
                //进行一个标志位判断,看一下最后结果是不是大于target
                flag = true;
                flagMe = true;
            }
            //进行判断,看一下当前的长度和当前记录的最小长度那个小
            if(flag && lengthRs > lengthNow){
                lengthRs = lengthNow;
            }
        }
        if(flagMe){
            return lengthRs;
        }
        return 0;
    }
}

但是可以发现,这种解法的效率不是很高
在这里插入图片描述
所以这里我们就延伸出了,解这个题的另外一种方式–滑动窗口法,注意看下图指针移动的过程。
刚开始都是起始位置:
在这里插入图片描述

然后j下标到2时,窗口和就大于7
在这里插入图片描述

这个时候我们就移动i指针往前一位
在这里插入图片描述
然后此时窗口和小于7,那就继续移动j指针
在这里插入图片描述
这个时候和又大于7,就移动i指针。
所以当大于target,就移动i指针,如果小于target就移动right指针。一直到j遍历完整个数组。所以对应解法如下:

public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int sum = 0;//定义当前的窗口内的值的和
        int lengthRs = Integer.MAX_VALUE;//定义窗口长度,这里取最大值
        for (int right = 0; right < nums.length; right++) {//定义右指针为right,然后开始遍历
            //每次都把结果加到sum上
            sum += nums[right];
            while(sum >= target){//如果窗口内的和大于target,就不能再继续移动right指针了,就要准备移动left指针
                lengthRs = Integer.min(lengthRs,right - left + 1);//这里更新下标长度
                sum -= nums[left];//当前窗口值大于target,所以减去左边指针的值
                left++;//更新left指针
            }
        }
        //如果是最后长度是Integer.MAX_VALUE,那就证明窗口所有的和加起来都小于target
        return lengthRs == Integer.MAX_VALUE ? 0 : lengthRs;
    }

暴力解法的时间复杂度是O(n^2),因为最差情况就是一个等差数列的和。
而滑动窗口时间复杂度是O(n),因为每一个元素都最多被遍历两次,也就是O(2n),去掉常数就是O(n)。

904. 水果成篮

链接:904.水果成篮
对应题目截图如下:
在这里插入图片描述

这道题是什么意思呢?
一句话总结一下:求只出现两个元素的最大子区间。
这一题和上一题相比,增加了对区间元素种类的控制。这题属于滑动窗口当中的计数问题,用arr数组来记录两个篮子中出现的水果数目,count来记录水果的种类,用count来控制窗口的滑动。如果说count <= 2,那么right就可以一直往右走;如果说count > 2,那就是left指针往右走。一边移动一变更新arr数组中对应的水果数目。

class Solution {
    public int totalFruit(int[] fruits) {
        int left = 0;
        int n = fruits.length;
        if(n < 2){
            return n;
        }
        int max = 2;//这里设置最长区间的下标,2就是最小值了
        int[] arr = new int[n];//创建一个数组用来记录每个数组出现的次数
        int count = 0;//用count来控制数组里面元素不为0的个数,也就是只能用两个篮子来装水果
        for (int right = 0; right < n; right++) {
            if (arr[fruits[right]] == 0){
                count++;//如果这个水果之前没有出现过,那么证明就是第一次入篮,count就+1
            }
            arr[fruits[right]] += 1;//更新每个水果对应出现次数
            while(left < n && count > 2){
               arr[fruits[left]]--;
               //这里注意了,因为我们这里还要用left来判断count,所以不能后面不能直接跟left++
                if(arr[fruits[left]] == 0) count--;
               left++;
           }
           max = Math.max(max,right - left + 1);//取连续下标区间最长的
        }
        return max;
    }
}

76. 最小覆盖子串

链接:76. 最小覆盖子串
截屏如下:
在这里插入图片描述
这个题,怎么说呢,和上面的题一样的思路,但是不一样的就是对count值的控制,说一下整体的思路吧:

一个一个来

这就是整体思路,最后判断一下特殊情况就好(看return行)

public class 最小覆盖子串 {
    public String minWindow(String s, String t) {
        int left = 0;//定义最后的左指针下标
        int right = 0;//定义最后的右指针下标
        int leftNow = 0;//当前的当前的左指针下标,用来求最小子串
        int count = 0;//用来控制窗口的滑动
        int minSize = Integer.MAX_VALUE;//记录最小子串的长度
        Map<Character,Integer> map = new HashMap<>();//用一个map表来映射对应的字符出现次数
        for(Character c:t.toCharArray()){
            //统计字符出现的次数
            map.put(c,map.getOrDefault(c,0) + 1);
        }
        for (int rightNow = 0; rightNow < s.length(); rightNow++) {
            Character c = s.charAt(rightNow);
            if(map.containsKey(c)){
                //记录当前字符对应的value值
                int cnt = map.get(c);
                map.put(c,cnt - 1);
                //如果cnt - 1 == 0,那就证明当前这个字符已经完全的在子串当中
                if(cnt == 1) count++;
            }
            //能进入while循环,就证明当前字符已经完全在子串中了
            while(count >= map.size()){
                //这个时候就要判断一下要不要更新子串
                if(minSize > (rightNow - leftNow + 1)){
                    right = rightNow;
                    left = leftNow;
                    minSize = right - left + 1;
                }
                Character rightC = s.charAt(leftNow);
                if(map.containsKey(rightC)){
                    int cnt = map.get(rightC);
                    map.put(rightC,cnt + 1);
                    //如果说cnt == 0,那就意味着什么呢?意味着原先字符是完全在子串中的,经过下面的leftNow++后就不在了
                    if(cnt == 0){
                        count--;
                    }
                }
                leftNow++;
            }

        }
        return minSize == Integer.MAX_VALUE ? "" : s.substring(left,right+1);
    }
}

总结

有没有发现,其实所谓的滑动窗口本质上还是一种双指针,解题思路其实大致还是可以使用我们的双指针模板,但是只是有相似之处,可以借鉴,这个和双指针还是有点区别的。

在这里插入图片描述

(自己用自己图不用去水印了吧~)

这里是我们之前的模板,但是这里我们这个模板就需要更换了
在这里插入图片描述

这里大致是需要这个模板的,但是记住了,不能太死。因为你最后实际i和j的值肯定是不在这个位置的。上面这个模板指针的位置是你最后正确答案的位置,什么意思呢?这样解释,逻辑位置和实际位置,我们的逻辑位置,就是我们要知道这个位置是正确的位置也是我们最后要返回的位置,实际地址,是我们实际上代码中i和j的位置。是不是还是有点抽象?(PS:这里好好理解一下,我对于这个概念的处理贯穿后续的内容)
我用上面76题的对应指针来进行讲解。

这两个就是逻辑指针的位置:
int left = 0;//定义最后的左指针下标
int right = 0;//定义最后的右指针下标
这两个就是实际指针的位置:
int leftNow = 0
int rightNow = 0

我们需要用实际指针位置来判断逻辑指针的位置,需要用实际指针来更新逻辑指针的位置。而且其实逻辑指针是可以不存在的!!!这里说一下,是可以不存在的!!
什么意思呢?

那说完了整体的思想,来说一下准确的实现。我们写滑动窗口的题主要是要记住一下几点:

一点一点来解答(用209题做具体模板,以下说法参考209题)

所有滑动窗口的题具体的精髓就在于如果移动这个窗口
在这里插入图片描述

这个玩意太暧昧了,但是就是一个我们当前逻辑指针记录的答案,和实际指针记录的答案的一个比较,然后如果说要扩充范围,就移动窗口结束位置。如果要缩小范围,就移动窗口起始位置。
下面给出代码模板

int left; //定义实际指针的位置

/*这里做对应题目的处理,1.我们要不要使用逻辑指针,如果使用我们就定义。但是你使用不
使用,你都要根据实际题目的要求来确定,如果方便就是使用。比如int变量求长度(比如209)
,比如String变量记录最短字符串(比如76)
2.确定我们中间要用哪一些处理来进行我们窗口滑动的判断
*/
for(int right = 0;;){//遍历右指针
	//动态更新实际窗口数据,只针对右指针
	
	while(实际答案已经得到,满足了窗口滑动条件,准备和我们的逻辑答案进行判断){
		//接着就是判断语句,是否要更新(位置不固定,根据题目自己选择)
		
		//更新当前实际窗口的值,逻辑上移动left指针,也就是left++。(为什么是逻辑上?因为这个时候可能判断语句在中当前这条语句的下面)

		//实际上在移动left,指针,也就是left++		
	}
}

return  正确答案;

所以其实也不难总结,我们实际上难点在下面

举报

相关推荐

0 条评论