850 字
2 分钟
数据结构-滑动窗口/单调队列
模板
P1886 【模板】单调队列 / 滑动窗口 - 洛谷
滑动窗口
滑动窗口的关键不是“两个指针的移动”,而是寻找一个具有单调性的合法边界
- 维持左右边界都不会退的一段范围,来求解很多子数组(串)的相关问题
- 关键:找到 范围和 答案指标之间的单调性关系,通过转化,创造(将有限个有效单调区间提炼出来等)
- 实现过程:双指针或者deque,用 简单变量 cnt,结构 哈希表等 来维护信息
- 时间复杂度优化的关键点:针对问题单调性的特点,左右指针只跑一遍,不回退,可以达到o(n)的复杂度
- 窗口根据题目需要,可以是 左闭右闭 左闭右开 左开右闭 左开右开
滑动窗口例题
209. 长度最小的子数组
int minSubArrayLen(int tar, vector<int>& nums) { int n=nums.size(); int ans=1e9,sum=0; for(int l=0,r=0;r<n;r++){ sum+=nums[r]; while(sum-nums[l]>=tar){ sum-=nums[l++]; } if(sum>=tar) ans=min(ans,r-l+1); } return ans==1e9?0:ans;}3. 无重复字符的最长子串
滑动窗口+哈希表
int lengthOfLongestSubstring(string s) { int n=s.size(); unordered_map<char,int> hp; int ans=0; for(int l=0,r=0;r<n;r++){ if(hp.find(s[r])!=hp.end()){ l=max(l,hp[s[r]]+1); } hp[s[r]]=r; ans=max(ans,r-l+1); } return ans;}76. 最小覆盖子串
string minWindow(string s, string t) { int n=s.size(),dt=t.size(); unordered_map<char,int> cnts; for(char c:t){ cnts[c]++; } int l=0,r=0,ll=-1,rr=-1; string res=""; int ans=1e9+1; for(;r<n;r++){ if(cnts.find(s[r])!=cnts.end()){ cnts[s[r]]--; if(s[r]>=0) dt--; } while(dt<=0){ if(r-l+1<ans){ ans=r-l+1; ll=l;rr=r; } if(cnts.find(s[l])!=cnts.end()){ cnts[s[r]]++; dt++; } l++; } } if(ans!=1e9+1){ for(int i=ll;i<=rr;i++){ res+=s[i]; } } return res;}134. 加油站
逻辑上想象数组复制了一遍,这样一个一维列表就可以遍历所有情况,实际处理是取余处理,滑动窗口中l作为起点遍历
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) { int n=gas.size(); vector<int> rems(n,0); for(int i=0;i<n;i++){ rems[i]=gas[i]-cost[i]; } for(int l=0,r=0,sum;l<n;l=r+1,r=l){ sum=0; while(sum+rems[r%n]>=0){ if(r-l+1==n) return l; sum+=rems[r%n]; r++; } } return-1;}1234. 替换子串得到平衡字符串
用静态数组模拟哈希表
int hp[128]={0};bool check(){ return hp['Q']>=0&&hp['W']>=0&&hp['E']>=0&&hp['R']>=0;}
int balancedString(string s) { int n=s.size(); hp['Q']=hp['W']=hp['E']=hp['R']=n/4; for(char c:s){ hp[c]--; } int ans=1e9+1; for(int l=0,r=0;r<n;r++){ hp[s[r]]++; while(l<=r&&check()&&hp[s[l]]>0){ hp[s[l]]--; l++; } if(check()){ ans=min(ans,r-l+1); } } return ans==1e9+1?0:ans;}992. K 个不同整数的子数组
做了两次尝试,第一次是窗口能缩就缩,导致如果在一个范围中无法确定右端点之后的点是否也可以囊括进这个范围中,会产生漏解;如果是在窗口扩的不能再扩的时候再缩,那么这个大范围中会有一些解会漏掉,也就是说,常规的滑动窗口解,l的单调性不管怎么样都会漏解,那么就要构造新的单调性,可以引入<=k的所有解,然后再跑一次<=k-1的所有解,两次答案相减能减掉<=k-1的所有非法解,同时又能算完所有的合法解
int subarraysWithKDistinct(vector<int>& nums, int k) { int n=nums.size(); vector<int> vis(n+1,0); auto wd = [&](auto&& self,int k,int n)->int{ fill(vis.begin(),vis.end(),0); int cnt=0,ans=0; for(int l=0,r=0;r<n;r++){ if(vis[nums[r]]==0) cnt++; vis[nums[r]]++; while(l<=r&&cnt>k){ vis[nums[l]]--; if(vis[nums[l]]==0) cnt--; l++; } ans+=r-l+1; } return ans; }; return wd(wd,k,n)-wd(wd,k-1,n);}如果“恰好”不好维护,就尝试转成“至多 / 至少”的差。
395. 至少有 K 个重复字符的最长子串
滑动窗口解,因为只含有小写字母,即使要找的答案没有单调性,也可以限制单次找的答案的长度(这题是限制答案中字母种类数)来维持一次遍历的单调性
int longestSubstring(string s, int k){ int n=s.size(); vector<int> cnts(26); int ans=0; auto check = [&](auto&& self)->bool{ for(int i=0;i<26;i++){ if(cnts[i]!=0&&cnts[i]<k){ return false; } } return true; }; for(int i=1;i<=26;i++){ fill(cnts.begin(),cnts.end(),0); for(int l=0,r=0,cnt=0;r<n;r++){ if(cnts[s[r]-'a']==0) cnt++; cnts[s[r]-'a']++; while(cnt>i){ cnts[s[l]-'a']--; if(cnts[s[l]-'a']==0) cnt--; l++; } if(cnt==i&&check(check)){ ans=max(ans,r-l+1); } } } return ans;}U192528 最大子序和 - 洛谷
单调队列
- 在滑动窗口的基础上,维护窗口中的最小值最大值,即维护双端队列里的单调性
- 实现:
- 右扩:和单调栈的规则一致
- 左缩:存在其他限制条件,在不破坏单调性的情况下需要删除当前最大值
- 目的: 维护依次成为最大值的可能性
单调队列例题
239. 滑动窗口最大值
经典用法
vector<int> maxSlidingWindow(vector<int>& nums, int k) { int n=nums.size(); vector<int> ans; deque<int> dq; for(int l=0,r=0;r<n;r++){ while(!dq.empty()&&nums[dq.back()]<nums[r]){ dq.pop_back(); } dq.push_back(r); while(r-l+1>k){ if(dq.front()<=l){ dq.pop_front(); } l++; } if(r-l+1==k) ans.push_back(nums[dq.front()]); } return ans;}1438. 绝对差不超过限制的最长连续子数组
经典用法 一个单调栈不能同时维护最大值最小值,需要两个单调栈同时维护最大值最小值
int longestSubarray(vector<int>& nums, int lmt) { int n=nums.size(),ans=0; deque<int> ma,mi; for(int l=0,r=0;r<n;r++){ while(!ma.empty()&&nums[ma.back()]<=nums[r]){ ma.pop_back(); } ma.push_back(r); while(!mi.empty()&&nums[mi.back()]>=nums[r]){ mi.pop_back(); } mi.push_back(r); while(nums[ma.front()]-nums[mi.front()]>lmt){ if(ma.front()==l) ma.pop_front(); if(mi.front()==l) mi.pop_front(); l++; } ans=max(ans,r-l+1); } return ans;} 分享
如果这篇文章对你有帮助,欢迎分享给更多人!
数据结构-滑动窗口/单调队列
https://blog.hydrodyio.xyz/posts/数据结构-滑动窗口-单调队列/ 部分信息可能已经过时
相关文章 智能推荐








