优选算法——滑动窗口


👇作者其它专栏

《C++起始之路》《算法》《数据结构与算法》


滑动窗口相关题解

1.长度最小的子数组

算法思路一(暴力)(超时):

【从前往后】枚举数组中的任意一个元素,把它当成起始位置。然后从这个【起始位置】开始,然后寻找一段最短的区间,使得这段区间的和【大于等于】目标值。

将所有元素作为起始位置所得的结果中,找到【最小值】即可。

class Solution{

public:

int minSubArrayLen(int target,vector<int> &nums){

//记录结果

int ret=INT_MAX;

int n=nums.size();

//枚举出所有满足和大于等于target的子数组【start,end】

//由于是取到最小,因此枚举的过程中要尽量让数组的长度最小

//枚举开始位置

for(int start=0;start<n;start++){

int sum=0;//记录从这个位置开始的连续数组的和

//寻找结束位置

for(int end=start;end<n;end++){

sum+=nums[end];//将当前位置加上

if(sum>=target){

ret=min(ret,end-start+1);

break;

}

}

}

return ret=INT_MAX?0:ret;

}

};

算法思路二(滑动窗口):

由于此问题分析的对象是【一段连续的区间】,因此可以考虑【滑动窗口】的思想来解决这道题。让滑动窗口满足:从i位置开始,窗口内所有元素的和小于target(那么当窗口内元素之和第一次大于等于目标值的时候,就是i位置开始,满足条件的最小长度)。

做法:将右端元素滑入窗口中,统计出当前窗口内的元素的和:

●若窗口内元素之和大于等于target:更新结果,并将左端元素滑出的同时继续判断是否满足条件并更新结果(因为左端元素可能很小,滑出去之后依旧满足条件)

●若窗口内元素和不满足条件:right++,下一个元素进入窗口

为什么滑动窗口可以解决问题,并且时间复杂度低?

●这个窗口寻找的是:以当前窗口最左侧元素(记为left1)为基准,符合条件的情况。也就是在这道题中,从left1开始,满足区间sum>= target时的最右侧(记为right1)能到哪里。

●我们既然已经找到从left1开始的最优的区间,那么就可以大胆舍去left1。但是若继续像方法一一样,重新开始统计left1的下一元素(left2)往后的和,一定会有大量重复的计算(因为我们在求第一段区间的时候,已经算出很多元素的和,这些和是可以在计算下次区间和的时候用上的)。

●此时,right1的作用就体现出来了,我们只需将left1这个值从sum中剔除。从right1这个元素开始,往后找满足left2元素的区间(此时right1也有可能是满足的,因为left1可能很小。sum剔除掉left1之后,依旧满足大于等于target)。这样我们就能省掉大量重复的计算。

●这样我们不仅能解决问题,而且效率也会大大提升。

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

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

算法思路一(暴力)(可以通过):

枚举【从每一个位置】开始往后,无重复字符的子串可以到达什么位置。找出其中长度最大的即可。

在往后寻找无重复子串能到达的位置时,可以利用【哈希表】统计出字符出现的频次,来判断什么时候子串出现了重复元素。

class Solution{

public:

int lengthOfLongestSubstring(string s){

int ret=0;//记录结果

int n=s.size();

//枚举从不同位置开始的最长重复子串

//枚举起始位置

for(int i=0;i<n;i++){

//创建一个哈希表,统计频次

int hash[128]={0};

//寻找结束为止

for(int j=i;j<n;j++){

hash[s[j]]++;//统计字符出现的频次

if(hash[s[j]]>1)//如果出现重复的

break;

//若无重复,就更新ret

ret=max(ret,j-i+1);

}

}

return ret;

}

};

算法思路二(滑动窗口):

研究的对象依旧是一段连续的区间,因此继续使用【滑动窗口】思想来优化。

让滑动窗口满足:窗口内所有元素都是不重复的。

做法:右端元素ch进入窗口的时候,哈希表统计这个字符的频次:

●若这个字符出现的频次超过1,说明窗口内有重复元素,那么就从左侧开始滑出窗口,直到ch这个元素的频次变为1,然后再更新结果

●若没有超过1,说明当前窗口没有重复元素,可以直接更新结果

3.最大连续1的个数 III

算法思路(滑动窗口):

本题的核心就是一段连续的1中间塞了k个0。

因此,我们可以把问题转化成:求数组中一段最长的连续区间,要求这段区间内的0的个数不超过k个。既然是连续空间,可以考虑使用【滑动窗口】来解决问题。

算法流程:

a.初始化一个大小为2的数组就可以当作哈希表hash了;初始化一些变量left=0,right=0,len=0;

b.当right小于数组大小的时候,一直下列循环:

i.让当前元素进入窗口,顺便统计到哈希表中;

ii.检查0的个数是否超标:

●如果超标,依次让左侧元素滑出窗口,顺便更新哈希表的值,直到0的个数恢复正常;

iii.程序到这里,说明窗口内元素是符合要求的,更新结果;

iv.right++,处理下一个元素;

c.循环结束后,len存的就是最终结果

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

算法思路(滑动窗口):

题目要求的是【左端+右端】两段连续的、和为x的最短数组,信息量稍微多一些,不易理清思路;我们可以转化成求数组内一段连续的、和为sum(nums)-x的最长数组。此时,就是熟悉的【滑动窗口】问题了。

算法流程:

a.转化问题:求target=sum(nums)-x。若target<0,问题无解;

b.初始化左右指针left=0,right=0(滑动窗口区间表示为[left,right),左右区间是否开闭很重要,必须设定与代码一致),记录当前滑动窗口内数组的和变量sum=0,记录当前满足条件数组的最大区间长度maxlen=-1;

c.当right小于等于数组长度时,一直循环:

i.若sum<target,右移右指针,直到变量和大于等于target,或右指针已经移到头;

ii.若sum>target,右移左指针,直到变量和小于等于target,或左指针已经移到头;

iii.若经过前两步的左右移动使得sum==target,维护满足条件数组的最大长度,并让下个元素进入窗口;

d.循环结束后,若len的值有意义,则计算结果返回,否则返回,-1。

5.水果成篮

算法思路(滑动窗口):

研究的对象是一段连续的区间,可以使用【滑动窗口】思想来解决问题。

**让滑动窗口满足:**窗口内的水果的种类只有两种。

**做法:**右端水果进入窗口的时候,用哈希表统计这个水果的频次。这个水果进来后,判断哈希表的大小:

●若大小超过2:说明窗口内水果超过了两种。那么就从左侧开始依次将水果滑出窗口,直到哈希表的大小小于等于2,然后更新结果;

●若没有超过2,说明当前窗口内水果的种类步超过两种,直接更新结果ret

算法流程:

a.初始化哈希表hash来统计窗口内水果的种类和数量;

b.初始化变量:左右指针left=0,right=0,记录结果的变量ret=0;

c.当right小于数组的大小的时候,一直执行下列循环:

i.将当前水果放入哈希表中;

ii.判断当前水果进来后,哈希表的大小;

●若超过2:

▢将左侧元素滑出窗口,并且在哈希表中将该元素的频次减1;

▢若这个元素的频次减1之后变成了0,就把该元素从哈希表中删除;

▢重复上述两个过程,直到哈希表中的大小不超过2;

iii.更新结果ret;

iv.right++,让下一个元素进入窗口;

d.循环结束后,ret存的就是最终结果。

6.找到字符串中所有字母异位词

算法思路(滑动窗口+哈希表):

●因为字符串p的异位词的长度一定与字符串p的长度相同,所以我们可以在字符串s中构造一个长度与字符串p的长度相同的滑动窗口,并在滑动中维护窗口中护每种字母的数量;

●当窗口中每种字母的数量与字符串p中每种字母的数量相同时,则说明当前窗口为字符串p的异位词;

●因此可以用两个大小为26的数组来模拟哈希表,一个来保存s中子串每个字符出现的个数,另一个来保存p中每一个字符出现的个数。这样就可以判断两字符串是否是异位词。

**优化:**使用cnt来保存有效字符的个数,不用创建判断函数

7.串联所有单词的子串

算法思路(暴力):

若我们把每一个单词看成一个一个字母,问题就变成了找到【字符串中所有的字母异位词】。无非就是之前处理的对象是一个一个的字符,我们这里处理的对象是一个一个的单词。

8.最小覆盖子串

算法思路(滑动窗口+哈希表):

●研究对象是连续的区间,因此可以尝试使用滑动窗口的思想来解决。

●如何判断当前窗口内所有字符是符合要求的呢?

我们可以使用两个哈希表,其中一个将目标串的信息统计起来,另一个哈希表动态的维护窗口内字符串的信息。

当动态哈希表中包含目标串中所有的字符,并且对应的个数都不小于目标串的哈希表中各个字符的个数,那么当前的窗口就是一种可行的方案。

算法流程:

a.定义两个全局的哈希表:2号哈希表hash2用来记录子串的信息,1号哈希表hash1用来记录目标串t的信息;

b.实现一个接口函数,判断当前窗口是否满足要求:

i.遍历两个哈希表中对应位置的元素:

●若t中某个字符的数量大于窗口中字符的数量,也就是1号哈希表某个位置大于2号哈希表。说明不匹配,返回false;

●全部匹配,返回true;

主函数中:

a.先将t信息放入1号哈希表中;

b.初始化一些变量:左右指针:left=0,right=0;目标子串的长度:minlen=INT_MAX;目标子串的起始位置:begin;(通过目标子串的起始位置何长度,我们就能找到结果)

c.当right小于字符串s的长度时,一直下列循环:

i.将当前遍历到的元素扔进2号哈希表中;

ii.检测当前窗口是否满足条件:

●判断当前窗口是否变小。若变小:更新长度minlen,以及字符串的起始位置begin;

●判断完毕后,将左侧元素滑出窗口,顺便更新2号哈希表;

●重复上面两个过程,直到窗口不满足条件;

iii.right++,遍历下一个元素;

d.判断minlen的长度是否等于INT_MAX:

i.若相等,说明没有匹配,返回空串;

ii.若不相等,说明匹配,返回s中从begin位置往后minlen长度的字符串。

**优化:**使用cnt来记录有效字符的种类,不用另创建判断的接口

相关推荐
csdn_aspnet1 小时前
奈飞工厂算法:个性化推荐系统的极限复刻
算法·netflix·奈飞
小白_ysf1 小时前
Vue 中常见的加密方法(对称、非对称、杂凑算法)
前端·vue.js·算法
多米Domi0112 小时前
0x3f 第49天 面向实习的八股背诵第六天 过了一遍JVM的知识点,看了相关视频讲解JVM内存,垃圾清理,买了plus,稍微看了点确定一下方向
jvm·数据结构·python·算法·leetcode
_F_y8 小时前
MySQL用C/C++连接
c语言·c++·mysql
兩尛8 小时前
c++知识点2
开发语言·c++
xiaoye-duck9 小时前
C++ string 底层原理深度解析 + 模拟实现(下)——面试 / 开发都适用
开发语言·c++·stl
Azure_withyou9 小时前
Visual Studio中try catch()还未执行,throw后便报错
c++·visual studio
琉染云月9 小时前
【C++入门练习软件推荐】Visual Studio下载与安装(以Visual Studio2026为例)
c++·visual studio
L_090711 小时前
【C++】高阶数据结构 -- 红黑树
数据结构·c++