【基础算法总结】滑动窗口一

在这里插入图片描述

点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃

1.长度最小的字数组

题目链接209.长度最小的字数组

题目分析:

在这里插入图片描述
注意题目说的是正整数数组,说明数组里面的数是大于等于0的数。因此这道题我们有一种优化的方法。

题目让找连续的子数组和大于或等于target,并且长度最小。有很多种情况,但是我们选择的是最小长度。
在这里插入图片描述

算法原理

不管什么题,首先我们一定会先想到的是暴力求解,因为只有暴力求解出来了,我们就可以在暴力求解的基础上进行优化!

解法一:暴力枚举出所有的子数组的和
两层for循环,O(N^2)
在这里插入图片描述

注意到此时暴力枚举是有优化的。之前是每次都从前往后走一遍,但是现在定义一个left,一个right初始化都指向0下标,sum+=nums[right],++right。
等到符合要求统计一下长度,

注意题目说的是一个正整数数组,都是大于等于0的数,这个sum是呈现出递增的状态的,单调递增!

在暴力求解中,此时right还要++,但是注意题目本来要求的就是最小长度,此时sum在加上往上走了一步的right的num[right],一定是满足sum>=target,但是len变成5了,一定不会是最终结果,因此当条件已经满足sum>=target ,right就不用动了。right后面也就不用再枚举了。

在这里插入图片描述
那现在让left+1,right和left指向同一下标,然后再重复上面过程,那有个问题,这段区间的和能不能直接算出来?

当然可以。现在sum=8,我只需要把让sum减去num[left],不就是现在left和right所在的区间和算出来吗。没有必要让right傻傻的回退然后重新加。因此right不动,更新sum=6.

在这里插入图片描述
因此我们从暴力枚举中发现两个优化:
一个是right后面不用枚举,一个left++,right不用回退,

所以我们可以使用双指针优化。

解法二:利用单调性,使用 “同向双指针” 来优化

当我们在暴力枚举的策略中发现left和right都是从左向右一个方向移动,我们就称为这两个指针叫做同向指针。同向双指针又称为滑动窗口。

什么是滑动窗口?
本质上是 “同向双指针”,left从左到右移动,right不回退,从左到右移动,用left和right一直维护这个区间的和,然后这两个指针从左向右移动的过程非常像一个窗口在这个数组里滑来滑去。

在这里插入图片描述

什么时候用滑动窗口?
利用单调性,用滑动窗口解决问题。
当我们发现在暴力求解时,两个指针都可以做到不回退,都是向同一个方向移动的时候,此时就可以用滑动窗口。

滑动窗口怎么用?

  1. 初始left=0,right=0,充当窗口左端点,右端点。用left,right标记窗口左区间,右区间。
  2. 进窗口(++right)
  3. 判断
    根据判断决定是否出窗口(++left)
  4. 更新结果
    2,3都有可能会更新结果,看题目要求

进窗口,判断,出窗口一直循环,直到right超过区间长度结束,更新结果看题目要求(进窗口,出窗口都有可能),

在这里插入图片描述

滑动窗口正确性
暴力枚举肯定对的,因为已经把所有子数组的情况都找出来了。虽然滑动窗口并没有把没有把所有情况都枚举出来,但是这里利用单调性,规避了很多没有必要的枚举行为。虽然没有把所有情况真正枚举出来,但是已经判断出有些子数组不是最终结果,已经把所有结果都考虑进来了,所以这种策略是跟暴力枚举是一样正确的。

滑动窗口时间复杂度
进窗口是一个循环,判断也是一个循环。两层循环套在一起。你会觉得时间复杂度O(N^2),但是不能看代码算时间复杂度,要看实际情况分析实际复杂度。实际我们只会让right向前移动,left也向前移动,即使时最坏情况,right移动到最后一个元素,lefi也移动到最后一个元素,因为总共操作次数最多n+n次 整体时间复杂度O(N)
在这里插入图片描述

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        // 使用滑动窗口解决问题

        int n=nums.size(),sum=0,len=INT_MAX;
        for(int left=0,right=0;right<n;++right)
        {
            sum+=nums[right];// 进窗口
            while(sum>=target)// 判断
            {
                len=min(len,right-left+1);// 更新结果
                sum-=nums[left++];// 出窗口
            }

        }
        return len==INT_MAX?0:len;


        // // 1.初始窗口
        // int left=0,right=0,len=INT_MAX;
        // int n=nums.size(),sum=0;
        // while(right<n)//2.进窗口
        // {
        //     sum+=nums[right];
        //     while(sum>=target)
        //     {
        //         //4.更新结果
        //         if(right-left < len)
        //             len=right-left;

        //         //3.出窗口
        //         sum-=nums[left];
        //         ++left;
        //     }
        //     ++right;
        // }
        // return len==INT_MAX?0:len+1;

    }
};

2.无重复字符的最长子串

题目链接3. 无重复字符的最长子串

题目分析:
在这里插入图片描述
子串和子数组都是连续的

在这里插入图片描述
算法原理

首先还是暴力枚举,然后根据暴力枚举进行优化。
以下面为例,两层for循环,但是下面找到的结果都是我们站在上帝角度,编译器并不知到什么时候结束。一般对应判断是否有重复元素,我们都可以用哈希表来解决问题。

使用哈希表,判断是否有重复元素,比如让你判断一个数组是否有重复,或者两个数组是否有重复都可以用哈希映射!
在这里插入图片描述
解法一:暴力枚举+哈希表(判断字符是否重复出现)
O(N^2)

根据解法一做优化,定义一个left,right指针。当right走到有重复的元素后,已经找到一个字串,其中left到right区间每个元素都已经进入hash表。
在这里插入图片描述

此时left向前走一步,但是这个区间还是有重复元素,因此left要走到没有重复的区间才行,
在这里插入图片描述

然后这个时候以前做法是right回退然后重新往下走,但是这里left到right区间元素本来就在hash表里,因此就不需要right回退了,而是向right继续向前走。然后重复上面过程,直到right走到结尾。结束!
在这里插入图片描述

这不就是滑动窗口的思想吗。双向指针,left往前走,right不回退一直往前走!

解法二:利用规律,使用 “滑动窗口” 解决问题

  1. left=0,right=0
  2. 进窗口
  3. 判断
    出窗口
  4. 更新结果

进窗口、判断、出窗口,更新结果是一个大循环过程。直到right到结尾循环结束。其中判断、出窗口是一个小循环。不过时间复杂度还是O(N).

注意更新结果可能在进窗口后,判断后,出窗口后,判断后任意一个地方,看题目要求

本题:

进窗口 ->-> 让字符进入哈希表
判断-> 窗口内出现重复元素
出窗口-> 从哈希表中删除该字符

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int hash[128]={0};//使用数组模拟哈希表
        int n=s.size(),ret=0;
        for(int left=0,right=0;right<n;++right)
        {
            hash[s[right]]++; // 进窗口
            while(hash[s[right]] > 1) // 判断
            {  
                hash[s[left++]]--;//出窗口      
            }
            ret=max(ret,right-left+1);//更新结果
        }
        return ret;
    }
};

总结一下:
利用单调性,使用 双指针 解决问题。
一般left和right,一个指向数组最左边,一个指向数组最右边,然后一次可以排除一批,再然后left++,–right,两个指针是对撞的

这里利用单调性或者利用规律,使用 滑动窗口 解决问题
滑动窗口也使用双指针解决问题,不过left一直从前往后走,right不回退从前往后走,两个指针是同向的。因此滑动窗口本质其实是同向双指针

3.最大连续1的个数 III

题目链接1004. 最大连续1的个数 III

题目分析:

在这里插入图片描述

题目说的翻转实际上是把0变成1的意思,最多翻转K次,说明小于等于K都是可以的。
在这里插入图片描述

拿到题我们开始肯定想的是暴力求解。如果直接暴力求解,遇到0->1了,那下一次在遍历就有问题了。因此我们换一个思路。这道题不是让转化后最大连续1的个数吗。我们转化为:找出最长的子数组,数组里0的个数不超过K个,这个数组里面0一定能够转化成1。

算法原理:

解法一:暴力枚举+zero计数器

伪代码,两层for循环,统计zero的个数,满足zero>k,统计此时数组长度,然后重新进入循环,注意每次zero都清0
在这里插入图片描述
然后我们根据暴力枚举,看看有没有优化的可能。定义两个指针left,right,right走到zero>k的位置,zero=3,大于k。
在这里插入图片描述
按照暴力求解left++,然后right回溯然后重新往后走。但是我们发现没有必要,现在left往前走一步,你会发现,right还是停留在老位置!这个区间不用在管的!直接丢弃。
在这里插入图片描述
因此,让left一直走到zero<=k的位置。然后right也根本不用回溯然后在重新走,而是直接往后走就行了。
在这里插入图片描述
根据上面的发现,当在暴力枚举中,发现left,right是同向移动的,利用这个规律,使用滑动窗口解决问题

解法二:利用规律,使用滑动窗口

  1. left=0,right=0
  2. 进窗口
  3. 判断
    出窗口
  4. 更新结果

进窗口 -> 如果是1,不理会。如果是0,计数器+1
判断 -> zero>k
出窗口 -> 如果是1,不理会。如果是0,计数器-1
更新结果:在判断之后在更新

在这里插入图片描述

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {

        int n=nums.size(),len=0,zero=0;
        for(int left=0,right=0;right<n;++right)
        {
            // 进窗口
            if(nums[right] == 0)
                ++zero;
            
            while(zero>k)//判断
            {
                if(nums[left++] == 0) //出窗口
                    --zero;
            }

            //更新结果
            len=max(len,right-left+1);
        }
        return len;

    }
};

4.将 x 减到 0 的最小操作数

题目链接1658. 将 x 减到 0 的最小操作数

题目分析:

在这里插入图片描述

这道题让每次从数组左右两边移除一个数,然后就是一个新的数组,然后再从新的数组再从左右两边移除一个数。但是如果真的硬着头皮开始做,其实是很困难的。
你并不知道每次是从最左边走还是最右边找。有可能这次左边下次右边或者还是左边,情况太复杂了。

在这里插入图片描述

因此我们可以利用正难则反的思想
正对面解题太难,那就想对立面,换个思路。
不是每次从左右两端找一个数吗,那可能找到情况就是a+b=x,a、b什么情况都要,但是中间这个连续区间的和不也是确定的吗sum-x,也就是这道题我们转换成,找出最长的子数组长度,所有元素的和正好等于sum-x,然后数组总长减去这段子区间长度不就是问题答案吗,如果没找到说明这个数组不存在将x减到0的数,直接返回-1
在这里插入图片描述

解法一:暴力求解

初始left,right指向同一下标,当right走到和大于target的时候,left往前走,按照暴力求解,right要回到和left相同下标,然后right在重新往前走,直到再次走到和大于target的地方停下来,然后重复上面过程。
在这里插入图片描述

但是今天这里不需要right回溯,因为right回溯后重新走到下面的位置,因为left已经往前走了,这段区间的和肯定是更小了,因此就不需要right回溯了。要么right不动,要么right往后走。
同向双指针 ----> 本质就是滑动窗口
在这里插入图片描述
解法二:使用滑动窗口

在这里插入图片描述

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int sum = 0;
        for (auto& e : nums)
            sum += e;
        int target = sum - x;

        //细节问题
        //数组里都是正整数,才能使用滑动窗口
        //如果让在一个正整数数组里找和为负数的根本不可能
        if (target < 0)
            return -1;

        sum = 0;
        int len = -1;
        for (int left = 0, right = 0; right < nums.size(); ++right)
        {
            sum += nums[right];//进窗口

            while (sum > target)//判断
            {
                sum -= nums[left++];//出窗口
            }

            if (sum == target)//更新结果
            {
                len = max(len, right - left + 1);
            }
        }

       if(len == -1) return len;
       else return nums.size()-len;
    }
};
  • 66
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 74
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值