👇作者其它专栏
滑动窗口相关题解
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来记录有效字符的种类,不用另创建判断的接口
