数组之滑动窗口

18 篇文章 0 订阅
17 篇文章 0 订阅

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。

那么滑动窗口如何用一个for循环来完成这个操作呢。

首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。

如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?

此时难免再次陷入 暴力解法的怪圈。

所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。

最小滑窗模板

给定数组 nums,定义滑窗的左右边界 i, j,求满足某个条件的滑窗的最小长度。

while j < len(nums):
    判断[i, j]是否满足条件
    while 满足条件:
        不断更新结果(注意在while内更新!)
        i += 1 (最大程度的压缩i,使得滑窗尽可能的小)
    j += 1

 最大滑窗模板

给定数组 nums,定义滑窗的左右边界 i, j,求满足某个条件的滑窗的最大长度。

while j < len(nums):
    判断[i, j]是否满足条件
    while 不满足条件:
        i += 1 (最保守的压缩i,一旦满足条件了就退出压缩i的过程,使得滑窗尽可能的大)
    不断更新结果(注意在while外更新!)
    j += 1

   关键的区别在于,最大滑窗是在迭代右移右边界的过程中更新结果,而最小滑窗是在迭代右移左边界的过程中更新结果。因此虽然都是滑窗,但是两者的模板和对应的贪心思路并不一样,而真正理解后就可以在lc.76,lc.904,lc.3, lc.1004写出非常无脑的代码。

    时间复杂度为:O(N), 空间复杂度为:O(N).

其实双指针和滑动窗口是有些许区别的。滑动窗口一句话就是右指针先出发,左指针视情况追赶右指针。可类比男生暗恋女生,两人都在往前走,但男生总是默默跟着女生走但又不敢超过她。因此,右指针最多遍历一遍数组,左指针也最多遍历一次数组,时间复杂度不超过O(2N)。接下来,如何判断滑动窗口内是否满足题设条件,有两种选择:(1) 要么你遍历这个滑窗,通过遍历来断滑窗是否满足需要O(N), 那么总的时间就退化为O(N^2), (2) 要么你选择字典,用空间换时间,那么判断划窗是否满足条件则需要 O(1),总时间为O(N).

lc904 水果成篮(最大滑窗)

白话题意:求满足某个条件(数组值最多就两类的连续数组,例如[1,2,2,1,2])的最长数组长度

public int totalFruit(int[] fruits) {
        int n=fruits.length;
        Map<Integer,Integer> cnt=new HashMap<>();
        int left=0,ans=0;
        for (int right = 0; right < n; right++) {
            cnt.put(fruits[right],cnt.getOrDefault(fruits[right],0)+1);
            while (cnt.size()>2){
                cnt.put(fruits[left],cnt.get(fruits[left])-1);
                if (cnt.get(fruits[left])==0){
                    cnt.remove(fruits[left]);
                }
                left++;
            }
            ans=Math.max(ans,right-left+1);
        }
        return ans;
    }

lc76 最小覆盖子串(最小滑窗)

public String minWindow(String s, String t) {
        int sLen = s.length();
        int tLen = t.length();
        if (sLen == 0 || tLen == 0 || sLen < tLen){
            return "";
        }

        char[] sCharArray = s.toCharArray();
        char[] tCharArray = t.toCharArray();

        // ascii('z') = 122,存字母对应的ASCII码
        int[] winFreq = new int[128]; //用来记录窗口内(也就是当前子串)的字符情况
        int[] tFreq = new int[128]; //用来记录t字符的频数
        for (char c : tCharArray){
            tFreq[c]++; //桶排序中统计出现次数的思想
        }

        //滑动窗口内部包含多少T中的字符,对应字符频数超过不重复计算
        int distance = 0; //用来记录t中字符在窗口中出现的频数
        int minLen = sLen + 1; //最小子串的长度,用于最后的substring
        int begin = 0; //开始下标,用于最后的substring

        //左右两个指针,也就是窗口的两个边界
        int left = 0;
        int right = 0;

        //左闭右开区间[left,right)
        while (right < sLen){ //当右指针没移动到头时
            //右指针向右移动逻辑
            char sCharRight = sCharArray[right]; //拿到当前右指针对应的字符
            if (tFreq[sCharRight] == 0){ //右指针的字符不是t中字符时
                right++; //向右移动
                continue;
            }

            if (winFreq[sCharRight]<tFreq[sCharRight]){
                distance++; //t中字符在窗口中出现的频数+1
            }
            winFreq[sCharRight]++; //窗口内(也就是当前子串)当前右指针对应的字符频次+1
            right++; //指针右移

            //执行该循环就说明,窗口的右边界已经找到了一个符合条件的窗口,但是左边界没有动,接下来尝试右移左边界缩小窗口,看看能不让子串更小
            while(distance == tLen){ //当t中字符在窗口中出现的频数满足题意时

                //更新最小子串长度
                if (right - left < minLen){
                    minLen = right - left;
                    begin = left;
                }

                //左指针向右移动的逻辑
                char sCharLeft = sCharArray[left]; //拿到当前左指针对应的字符
                if (tFreq[sCharLeft] == 0){ //左指针的字符不是t中字符时
                    left++; //向右移动
                    continue;
                }

                //当前左指针对应的字符频次和t串对应的字符频次相等
                if (winFreq[sCharLeft] == tFreq[sCharLeft]){
                    distance--; //t中字符在窗口中出现的频数-1(因为左指针要右移,所以频次会减少)
                }
                winFreq[sCharLeft]--; //窗口内(也就是当前子串)当前左指针对应的字符频次-1
                left++; //左指针右移
            }
        }

        if (minLen == sLen+1){ //最小长度没有变化,说明无法匹配,直接返回空串
            return "";
        }
        return s.substring(begin, begin+minLen);

    }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天辰尽落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值