滑动窗口详解

目录

一、滑动窗口的特定步骤:

二、题目解析

1、⻓度最⼩的⼦数组---点击跳转题目

3、最⼤连续 1 的个数 III----点击跳转题目

4、将 x 减到 0 的最⼩操作数----点击跳转题目

5、⽔果成篮----点击跳转题目


滑动窗口是双指针算法中细分的一种,它由暴力枚举算法优化而来,解决的是同向双指针问题;当一个题目在暴力解法中发现枚举时其实可以不用把“指针”往回走,就可以使用滑动窗口的思想来解决。也就是滑动窗口通过题目的某种性质,忽略了很多无效枚举,维护两个指针间的区间保持某种性质,从而把时间复杂度从O(n^2)优化到O(n)

只要题目的研究对象是一段连续区间或者能转化为一段连续区间,就可以考虑用滑动窗口来做,思路不清晰时可以先想题目的暴力枚举,从中优化而来

一、滑动窗口的特定步骤:

  • 定义指针left、right
  • 确定如何进窗口
  • 判断(指针内区的某种性质进入循环)用while循环做判断
  • 通过判断不断出窗口,使得指针内的区间满足性质
  • 确定在哪一步更新结果

滑动窗口的代码形式是双重循环,外层是right指针遍历,内层是为了指针内区间满足某种性质而不断出窗口;虽然是双重循环,但是时间复杂度只有O(n)

二、题目解析

1、⻓度最⼩的⼦数组---点击跳转题目

由于此问题分析的对象是「⼀段连续的区间」,因此可以考虑「滑动窗⼝」的思想来解决这道题。 让滑动窗⼝满⾜:从 i 位置开始,窗⼝内所有元素的和⼩于 target (那么当窗⼝内元素之和 第⼀次⼤于等于⽬标值的时候,就是 i 位置开始,满⾜条件的最⼩⻓度)。 做法:将右端元素划⼊窗⼝中,统计出此时窗⼝内元素的和:

▪ 如果窗⼝内元素之和⼤于等于 target :更新结果,并且将左端元素划出去的同时继续判 断是否满⾜条件并更新结果(因为左端元素可能很⼩,划出去之后依旧满⾜条件)

▪ 如果窗⼝内元素之和不满⾜条件: right++ ,让下⼀个元素进⼊窗⼝。

代码:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
        int l = 0, r = 0;
        long long sum = 0;
        int len = INT_MAX;

        while(r < nums.size())
        {
            sum += nums[r];//进窗口
            while(sum >= target)//判断
            {
                len = min(r - l + 1,len);//更新结果
                sum -= nums[l++];//出窗口
            }
            r++;
        }
       
       return len == INT_MAX ? 0 : len;
    }
};

时间复杂度:虽然代码是两层循环,但是我们的 left 指针和 right 指针都是不回退的,两者 最多都往后移动 n 次。因此时间复杂度是 O(n)

2、⽆重复字符的最⻓⼦串----点击跳转题目

暴力思路:枚举「从每⼀个位置」开始往后,⽆重复字符的⼦串可以到达什么位置。找出其中⻓度最⼤的即可。在往后寻找⽆重复⼦串能到达的位置时,可以利⽤「哈希表」统计出字符出现的频次,来判断什么时候⼦串出现了重复元素。

优化:研究的对象依旧是⼀段连续的区间,因此继续使⽤「滑动窗⼝」思想来优化。
让滑动窗⼝满⾜:窗⼝内所有元素都是不重复的。
做法:右端元素 ch 进⼊窗⼝的时候,哈希表统计这个字符的频次:
▪ 如果这个字符出现的频次超过 1 ,说明窗⼝内有重复元素,那么就从左侧开始划出窗⼝,
直到 ch 这个元素的频次变为 1 ,然后再更新结果。
▪ 如果没有超过 1 ,说明当前窗⼝没有重复元素,可以直接更新结果

代码:

class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        int hash[128];//下标表示字符,数组存储字符个数,可以快速查找每个字符个数
        int l = 0, r = 0;
        int len = 0;

        while(r < s.size())
        {
            hash[s[r]]++;//进窗口
            while(hash[s[r]] > 1)//判断
                hash[s[l++]]--;//出窗口
            len = max(len,r - l + 1);//更新结果
            r++;
        }
        return len;
    }
};

3、最⼤连续 1 的个数 III----点击跳转题目

不要去想怎么翻转,不要把问题想的很复杂,这道题的结果⽆⾮就是⼀段连续的 1 中间塞了 k 个 0 嘛。 因此,我们可以把问题转化成:求数组中⼀段最⻓的连续区间,要求这段区间内 0 的个数不超 过 k 个。 比特就业课 既然是连续区间,可以考虑使⽤「滑动窗⼝」来解决问题。

滑动窗口的性质:区间内至多有k个0,维护这个性质即可

代码:

class Solution {
public:
    //转化:找到最长的子数组,0的个数不超过k个
    int longestOnes(vector<int>& nums, int k) 
    {
        int l = 0, r = 0, len = 0;
        int zero = 0;

        while(r < nums.size())
        {
            if(nums[r] == 0) zero++;//进窗口
            while(zero > k)//判断性质是否满足
            {
                if(nums[l++] == 0) zero--;//出窗口
            }
            len = max(len,r - l + 1);//性质满足时更新结果
            r++;
        }
        return len;
    }
};

4、将 x 减到 0 的最⼩操作数----点击跳转题目

题⽬要求的是数组「左端+右端」两段连续的和为 x 的最短数组,正难则反,我们可以转化成求数组内⼀段连续的、和为 sum(nums) - x 的最⻓数组。此时,就是熟悉的「滑动窗⼝」问题了

target = sum - x

滑动窗口的性质:区间内和为target

题目所求的最小操作数也就是 nums的个数减去最长子数组

代码:

class Solution {
public:
    //转化:找到最长子数组,和为 sum - x
    int minOperations(vector<int>& nums, int x) 
    {
        int sum=0, target, len = -1;
        // sum = accumulate(nums.begin(), nums.end(), 0);
        for(auto e : nums) sum += e;
        target = sum - x;
        if(target < 0) return -1; //sum < x的情况不可能把x减为0
        int l = 0, r = 0;
        
        long long s = 0;//区间内的和
        while(r < nums.size())
        {
            s += nums[r];//进窗口
            while(s > target)//需要的是s等于target
            {
                s -= nums[l++];//出窗口
            }
            if(s == target) //上面循环跳出来时s可能等于或小于target
                len = max(len,r - l + 1);//更新结果
            r++;
        }
        if(len == -1) return len;
        else return nums.size() - len;
    }
};

5、⽔果成篮----点击跳转题目

研究的对象是⼀段连续的区间,可以使⽤「滑动窗⼝」思想来解决问题。 让滑动窗⼝满⾜:窗⼝内⽔果的种类只有两种。

做法:右端⽔果进⼊窗⼝的时候,⽤哈希表统计这个⽔果的频次。这个⽔果进来后,判断哈希表的⼤⼩:

▪ 如果⼤⼩超过 2:说明窗⼝内⽔果种类超过了两种。那么就从左侧开始依次将⽔果划出窗 ⼝,直到哈希表的⼤⼩⼩于等于 2,然后更新结果;

▪ 如果没有超过 2,说明当前窗⼝内⽔果的种类不超过两种,直接更新结果 len。

代码:

class Solution {
public:
    //找出一个最长子数组,数组中最多两种水果
    int totalFruit(vector<int>& fruits) 
    {
        const int N = 1e5 + 10;
        //下标表示水果种类,数组元素表示区间中这种水果放了几次
        int hash[N] = {0};
        int l = 0, r = 0;
        int kinds = 0,len = 0;//kinds表示区间内水果种类数
        while(r < fruits.size())
        {
           if( hash[fruits[r]]++ == 0) kinds++;
          
           while(kinds > 2)
           {
                //出窗口
                hash[fruits[l]]--;
                if(hash[fruits[l]] == 0) kinds--;
                 l++;
           }
            len = max(len,r - l + 1);
            
            r++;
        }
        return len;
    }
};

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值