前言:
①单调队列模板(左边是队头hh,右边是队尾tt)
首先维护两个指针hh和tt的位置
常见模型:找出滑动窗口中的最大值/最小值 int hh = 0, tt = -1; for (int i = 0; i < n; i ++ ) { while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口 while (hh <= tt && check(q[tt], i)) tt -- ; q[ ++ tt] = i; }
AcWing 154. 滑动窗口(模板题)--单调队列&& Leetcode 239. 滑动窗口最大值
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N], q[N]; //q存idx
int n, k;
int main(){
cin >> n >> k;
for(int i = 0; i < n; i ++) cin >> a[i];
int hh = 0, tt = -1;
// Q1: 输出每个窗口中的最小值
for(int i = 0; i < n; i ++){ // 二重维护
// step1: 维护一下窗口长度不超过k(左边队头hh的位置!)
if(hh <= tt && i - k + 1 > q[hh]) hh ++;
// step2: 维护单调队列q是单调增的即可
while(hh <= tt && a[i] < a[q[tt]]) tt --;
q[++ tt] = i;
if (i + 1 >= k) // 只有当有了一个完整的窗口后才能进行输出
cout << a[q[hh]] << " ";
}cout << endl;
// Q2: 输出每个窗口中的最大值
hh = 0, tt = -1; // 别忘记要先重置
for(int i = 0; i < n; i ++){ // 二重维护
// step1: 维护一下窗口长度不超过k(左边队头hh的位置!)
if(hh <= tt && i - k + 1 > q[hh]) hh ++;
// step2: 维护单调队列q是单调减的即可
while(hh <= tt && a[i] > a[q[tt]]) tt --;
q[++ tt] = i;
if (i + 1 >= k)
cout << a[q[hh]] << " ";
}
}
Leetcode 3. 无重复字符的最长子串
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.length(), res = 0;
unordered_set<int> Set; // 用于判断当前滑动窗口中存在字符情况
int l = 0, r = -1; // 类模板中的hh和tt(只不过这里不考虑单调性)
for (int i = 0; i < n; i ++){
while(l <= r && Set.count(s[i])){
l ++; // 维护出一个合法的单调队列,进行res更新
if(s[i] == s[l - 1]) break; // 剔除掉那个重复的词之后即完成了维护
else Set.erase(s[l - 1]); // 剔除非重复char需要在set中删除
}
r ++;
Set.insert(s[r]);
res = max(res, r - l + 1);
}
return res;
}
};
Leetcode 438. 找到字符串中所有字母异位词
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
int n = s.length(), m = p.length();
string wind = "";
unordered_set<char> tmp;
vector<int> res;
vector<int> tong(26, 0); // 对应26个字母
for(auto& v: p){
tong[v - 'a'] += 1;
tmp.insert(v);
}
// 生成原型字符串
string tmplate = "";
for(int i = 0; i < 26; i ++){
tmplate += "#" + to_string(tong[i]);
tong[i] = 0; // 便于下面重复利用
}
for(int i = 0; i < n; i ++){
if(tmp.count(s[i]) == 0){
wind = ""; // 清空窗口内容
fill(tong.begin(), tong.end(), 0);
continue;
}
wind += s[i];
tong[s[i] - 'a'] += 1;
if(wind.length() > m){ // 维护区间长度
auto it = wind.begin();
wind.erase(it); // 删除首部
tong[s[i - m] - 'a'] -= 1;
}
if(wind.length() == m){
string back = "";
for(int j = 0; j < 26; j ++)
back += "#" + to_string(tong[j]);
// cout << wind << " " << back << endl;
if(back == tmplate) res.push_back(i - m + 1);
}
}
return res;
}
};
这个代码一个可以优化的地方:直接使用两个vector进行比较是否相等即可。进一步优化:能不能不采用2个集合呢?只使用一个cnt集合,右指针遇到一个cnt[right]减去一个,然后直接维护左指针left : 如果右指针指向的cnt数组位置<0,那么cnt[left] ++; left ++;维护到cnt[right]>=0即可。维护完毕后检查此时的窗口大小是否等于p.length,相等就记录ans即可。代码如下:.
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> res, cnt(26, 0);
int n = s.length(), m = p.length();
for(auto& v: p) cnt[v - 'a'] ++;
int l = 0, r = 0;
for(; r < n; r ++){
cnt[s[r] - 'a'] --;
while(cnt[s[r] - 'a'] < 0){
cnt[s[l] - 'a'] ++;
l ++;
}
if(r - l + 1 == m) res.push_back(l);
}
return res;
}
};
正确性分析:因为只在r - l + 1的时候才可加入res,而l和r维护的窗口中每有一个元素就必然会让cnt中的元素--,那么除了完美对应上全减为0,否则一定会遇到right指针--后检测为<0,那么就会让left指针++,剔除掉多减的元素。
Leetcode 2024. 考试的最大困扰度
确定核心算法:
由于子串越长,越无法满足要求,有单调性,可以用滑动窗口解决
核心步骤:
class Solution {
public:
int maxConsecutiveAnswers(string answerKey, int k) {
int n = answerKey.length();
int left = 0, ans = 0, right = 0, cnt[2]{}; // cnt用列表初始化为0
for(; right < n; right ++){
cnt[(answerKey[right] >> 1) & 1] ++;
while (cnt[0] > k && cnt[1] > k) // 核心: 窗口内T F都大于k的时候那么就不符合要求了(此后子串越长,越不符合要求, 因此使用滑动窗口, 维护窗口)
cnt[(answerKey[left ++] >> 1) & 1] --;
ans = max(ans, right - left + 1);
}
return ans;
}
};
代码实现时,由于 T 和 F 的 ASCII 值除以 2 后的奇偶性不同,也就是它们二进制的次低位不同,可以改为统计二进制次低位。