基础算法:滑动窗口
目录
一、长度最小的子数组
二、无重复字符的最长子串
三、最大的连续1的个数III
四、将x减到0的最小操作数
五、水果成篮
六、找到字符串中所有字母异位词
七、串联所有单词的子串
八、最小覆盖子串
滑动窗口算法,本质是根据单调性,利用同向双指针创建窗口,然后遍历数组。
一、长度最小的子数组
209. 长度最小的子数组 - 力扣(LeetCode)
创建int变量ans,left和right首先设置在0位置,创建sum,sum+=[right],判断是否大于等于我的target,不是right++,继续判断,等到sum>=target时,right后的数据不再需要遍历,因为数组所有元素为正,遍历后边的数组只会让数据变大,由此暂停,找到第一个,记录len,len修改:只需将ans等于ans和right-left+1的min即可)sum减去【left】left可以往后遍历,判断,大于继续上述操作,小于right++,sum+【right】。即每次出窗口进窗口更新sum判断是否更新len判断是否更新ans。时间复杂度为n。
特殊情况下所有数值加起来都不够超过target,此时ans并未操作一次,判断是否等于初始值,等于返回0,不等于返回本身。
class Solution {
public:int minSubArrayLen(int target, vector<int>& nums) {int ans=INT_MAX,left=0,right=0,sum=0;int n=nums.size();while(right<n){sum+=nums[right]; //进窗口while(sum>=target) //持续 判断{ans=min(ans,right-left+1); //直接用min即可 sum-=nums[left++]; //出窗口}++right; //结束判断,更新右窗口} return ans==INT_MAX?0:ans; //特殊情况处理}
};
二、无重复字符的最长子串
3. 无重复字符的最长子串 - 力扣(LeetCode)
对于重复字符我们可以创建一个哈希表来判断
创建left,right,均从0开始,right往右遍历,【right】存进哈希表(计数桶) ,判断有没有重复元素,没有继续遍历将数据进窗口,遇到重复元素暂停,更新len,将left更新到窗口内的重复元素位置的下一个位置因为这里涉及到遍历,我们还是得按一个一个过去,(比如上图a,已经是让第一个窗口变最大的右边界,left从d到a遍历撑死都不会比这个长度还大)然后right++,判断,重复操作。
其实这题还能转化成最终返回子串,只需要在过程中多更新起始位置即可,最后return s.substr( begin,length)
class Solution {
public:int lengthOfLongestSubstring(string s) {if(s=="")return 0; //特殊情况直接返回提高效率int ans=0,left=0,right=0,hash[128]={0};//数组模拟哈希映射int n=s.size();while(right<n){++hash[s[right]]; //进窗口while(hash[s[right]]>1){--hash[s[left++]]; //出窗口,到区间内重复元素下一位置}//写在出窗口外边,保证每一次的right操作后都能检查更新ans=max(ans,right-left+1); //每次操作后都更新长度,直接max判定++right; }return ans;}
};
三、最大的连续1的个数III
1004. 最大连续1的个数 III - 力扣(LeetCode)
法一:暴力+0计数器,暴力遍历同时统计0的个数,更新长度,0大于k时才截至,说明这个位置往前的最大连续1子串就这么长了.
法二:滑动窗口,基于暴力枚举的思路,就会发现这是一道很明显的滑动窗口题,特别之处在于对0的统计,创建left right,从0开始,往后遍历,if(!【right】)count+=1,更新长度,等到count>k,让while循环(count>k)让左开始回收窗口,if(!【left】)count-=1;由此大功告成,复杂度n.
class Solution {
public:int longestOnes(vector<int>& nums, int k) {int n=nums.size();int left=0,right=0,count=0,ans=0;while(right<n){if(!nums[right])++count; //统计0while(count>k){if(!nums[left++])--count;}ans=max(ans,right-left+1); //更新长度,max自动判定++right;}return ans;}
};
四、将x减到0的最小操作数
1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)
保证一段连续的子数组的和为数组元素和减去x即可
这不就是滑动窗口,不多赘述
class Solution {
public:int minOperations(vector<int>& nums, int x) {int n=nums.size();int left=0,right=0,sum=0,target,ans=-1;for(int& e:nums){sum+=e;}//特殊情况处理if(sum<x){return -1;}target=sum-x;sum=0;while(right<n){sum+=nums[right];while(sum>target){sum-=nums[left++];}if(sum==target){ans=max(ans,right-left+1);}++right;}return ans<0?-1:n-ans;}
};
五、水果成篮
904. 水果成篮 - 力扣(LeetCode)
滑动窗口,突破点就在于如何表示元素种类大于2,
思路如下:
学完map后我们知道我们可以将key代表元素种类,进窗口利用原数组【right】,并利用map的特性将value++,从而定义了一个map的存储单位,利用size函数当大于2时,while循环,先让hash.fruits.left出窗口,如果hash.fruits.left==0,也就是value为0,erase hash【nums【left++】】即可。
class Solution {
public:int totalFruit(vector<int>& fruits) {int left=0,right=0,ans=0;int n=fruits.size();map<int,int> hash; //便于实现种类的统计while(right<n){hash[fruits[right]]++; //进窗口的同时相当于直接创建了一个hash单位while(hash.size()>2){--hash[fruits[left]]; //不着急++leftif(hash[fruits[left]]==0) //判断value是否为0,为0删除这个hash单位,退出while{hash.erase(fruits[left]);}++left;}ans=max(ans,right-left+1);++right;}return ans;}
};
六、找到字符串中所有字母异位词
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
异位词我们可以借助哈希表解决,
分析题干,即求和p一样长的区间,且区间内元素种类相同,且每个元素出现次数与之一致。创建left right=0,因为都是小写字母,创建 两个 hash【26】便于比对,利用ascii码来判断位置,我们可以创建一个变量count,count最终等于p的size,进窗口的元素如果是hash2里的,且这个数目小于hash2【该数-‘a'】就说明有效,count++,right-left+1>m时要进行出窗口,出窗口判断出的元素hash1是否
count:匹配上的元素个数。
class Solution {
public:vector<int> findAnagrams(string s, string p) {int hash1[26]={0},hash2[26]={0};//统计元素种类及其个数,方便后续判断for(char&e:p){hash2[e-'a']++;}int left=0,right=0,count=0,m=p.size();vector<int> ans;while(right<s.size()){char in=s[right];//进窗口,维护countif(++hash1[in-'a']<=hash2[in-'a'])++count;while(right-left+1>m){char out=s[left++];//出窗口,维护countif(hash1[out-'a']--<=hash2[out-'a'])--count;}if(count==m){ans.push_back(left);}++right;}return ans;}
};
七、串联所有单词的子串
30. 串联所有单词的子串 - 力扣(LeetCode)
因为word里所有的string的大小都是一样的,然后要求时这个string内部顺序不变,不同string顺序可以改变,想象一下如果可以把每个string当作一个字母,这不就是上一道异位词的题目吗,将字串存进来的操作可以通过substr(pos,len)来实现。
class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {//先统计数据unordered_map<string,int> hash1;for(auto&e:words){++hash1[e];}int left,right,len=words[0].size(),n=s.size(),count;unordered_map<string,int> hash2;vector<int> ans;//遍历len次,开始位置为0,1,2...len-1for(int i=0;i<len;++i){left=right=i;//清零!count=0;hash2.clear();while(right+len<=n){string in=s.substr(right,len);//如果hash1里没有in,还会有一步插入key,初始value的操作if(hash1.count(in)&&++hash2[in]<=hash1[in])++count;//>=时说明hash2里多存进来了一个单词while(right-left>=words.size()*words[0].size()){//substr提取子串string out=s.substr(left,len);if(hash1.count(out)&&hash2[out]--<=hash1[out])--count;left+=len;}if(count==words.size()){ans.push_back(left);}right+=len;}}return ans;}
};
八、最小覆盖子串
76. 最小覆盖子串 - 力扣(LeetCode)
一样的思路,存一个begin为left即可
class Solution {
public:string minWindow(string s, string t) {unordered_map<char,int> hash1;for(auto&e:t){++hash1[e];}unordered_map<char,int> hash2;int left=0,right=0,count=0;//不要更新子串,更新子串起始位置和长度更方便int minlen=INT_MAX,begin=-1;while(right<s.size()){//进窗口,维护countif(hash1.count(s[right])&&++hash2[s[right]]<=hash1[s[right]]){++count;}//符合条件,析出子串while(count==t.size()){//如果字符不是t里的直接++leftwhile(!hash1.count(s[left])){left++;}if(right-left+1<minlen){minlen=right-left+1;begin=left;}//判断这个位置的left是不是有效的//有效->终止,寻找下一个相等的机会//无效->再进入循环->最终析出子串if(hash2[s[left]]--<=hash1[s[left]])--count;++left;}++right;}return begin==-1?"":s.substr(begin,minlen);}
};
此篇完。