这里我只给出ac代码和其应用,其实我也很想给出详细教学,
但大佬们的题解是真的通俗易懂(会在文章下方留下)。
一定要理解它,不要死记代码 ! ୧꒰•̀ᴗ•́꒱୨
题目->(滑动窗口求最值):
给定一个大小为 n≤1e6 的数组。
有一个大小为 kk的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 k 个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为 [1 3 -1 -3 5 3 6 7],k 为 3。
你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
输入格式:
输入包含两行。
第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。
第二行有 n 个整数,代表数组的具体数值。
同行数据之间用空格隔开。
输出格式:
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。
输入样例:
8 3
1 3 -1 -3 5 3 6 7
输出样例:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
AC代码(数组模拟):
哒哒要变强 :
#include <iostream>
using namespace std;
const int N = 1000100;
//单调队列一般用双端队列保证其单调性
int a[N], q[N], n, k;
//队头和队尾,在队尾插入,队头获取
int front = 0, tail = -1;
int main() {
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
//先找每个窗口的最小值
for (int i = 0; i < n; i++) {
//如果当前队头在数组的下标小于当前窗口的最小下标,这个窗口就不包含这个元素了那么无论如何都要剔除队头这个元素
//所以要在队头删除这个元素
if (front <= tail && i - k + 1 > q[front]) front++;
//保证单调性,在队尾删除(为什么要在队尾删除,简单来说在队头删除不能保证单调
//比如-3 5为当前队列,当前的元素为3,如果在队头操作,那么按照a[i] <= a[q[front],有3 > -3,因此不做删除操作
//但是接下来就出现问题了,3就要入队了。此时队列就是-3 5 3,不符合单调性了!
//但如果在队尾操作,按照a[i] <= a[q[tail],有3 < 5,就要让5出队
//之后3入队,队列就是-3 3,满足单调性
while (front <= tail && a[i] <= a[q[tail]]) tail--;
q[++tail] = i;
//队头为窗口的最小值
if (i >= k - 1) printf("%d ", a[q[front]]);
}
printf("\n");
//这次找最大值,同理
front = 0, tail = -1;
for (int i = 0; i < n; i++) {
if (front <= tail && i - k + 1 > q[front]) front++;
while (front <= tail && a[i] >= a[q[tail]]) tail--;
q[++tail] = i;
if (i >= k - 1) printf("%d ", a[q[front]]);
}
}
AC代码(deque):
Hasity :
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 1000010;
int a[N];
int main()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i ++ ) cin >> a[i];//读入数据
deque<int> q;
for(int i = 1; i <= n; i++)
{
while(q.size() && q.back() > a[i]) //新进入窗口的值小于队尾元素,则队尾出队列
q.pop_back();
q.push_back(a[i]);//将新进入的元素入队
if(i - k >= 1 && q.front() == a[i - k])//若队头是否滑出了窗口,队头出队
q.pop_front();
if(i >= k)//当窗口形成,输出队头对应的值
cout << q.front() <<" ";
}
q.clear();
cout << endl;
//最大值亦然
for(int i = 1; i <= n; i++)
{
while(q.size() && q.back() < a[i]) q.pop_back();
q.push_back(a[i]);
if(i - k >= 1 && a[i - k] == q.front()) q.pop_front();
if(i >= k) cout << q.front() << " ";
}
}
AC代码(暴力):(了解就行,会超时)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1000010;
int n, m;
int a[N];
int main()
{
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = m; i <= n; i ++)
{
int mx = a[i];
for(int j = i - m + 1; j <= i; j ++)
{
mx = min(mx, a[j]);
}
printf("%d ", mx);
}
puts("");
for(int i = m; i <= n; i ++)
{
int mx = a[i];
for(int j = i - m + 1; j <= i; j ++)
{
mx = max(mx, a[j]);
}
printf("%d ", mx);
}
}
单调队列的应用:切蛋糕 、合并果子 、烽火传递 (单调队列在优化动态规划中的应用)
(补充)滑动窗口的应用:最小覆盖字串(有详细题解)、最短超串、找到字符串中所有字母异位词、字符串排列可以帮助我们稳固滑动窗口算法。
最后,非常感谢您的阅读,有问题就评论,必答!(>▽<)
二维单调队列:(补充)
所谓二维,就是对二维数组的列和行分别用一次一维的单调队列(先行先列不影响)。这里的题解是先行再列的。(建议先看代码,再看图)
经过两次一维单调队列的操作后,每个(3 , 4)的子矩阵的最小值(min2)都被存于图中阴影部分,实际就是存于每个子矩阵的右下角。然后在遍历这个阴影部分就🆗了。
最大值亦然~~~
#include<iostream>
#include<cstring>
using namespace std;
const int N=1010,mod=998244353;
int g[N][N],min1[N][N],min2[N][N],max1[N][N],max2[N][N];
int n,m,a,b;
void getmin1(int id){
int q[N],tt=-1,hh=0;
for(int i=1;i<=m;i++){
while(tt>=hh&&i-q[hh]+1>b)hh++;
while(tt>=hh&&g[id][q[tt]]>=g[id][i])tt--;
q[++tt]=i;
if(i>=b)min1[id][i]=g[id][q[hh]];
}
}
void getmin2(int id){
int q[N],tt=-1,hh=0;
for(int i=1;i<=n;i++){
while(tt>=hh&&i-q[hh]+1>a)hh++;
while(tt>=hh&&min1[q[tt]][id]>=min1[i][id])tt--;
q[++tt]=i;
if(i>=a)min2[i][id]=min1[q[hh]][id];
}
}
void getmax1(int id){
int q[N],tt=-1,hh=0;
for(int i=1;i<=m;i++){
while(tt>=hh&&i-q[hh]+1>b)hh++;
while(tt>=hh&&g[id][q[tt]]<=g[id][i])tt--;
q[++tt]=i;
if(i>=b)max1[id][i]=g[id][q[hh]];
}
}
void getmax2(int id){
int q[N],tt=-1,hh=0;
for(int i=1;i<=n;i++){
while(tt>=hh&&i-q[hh]+1>a)hh++;
while(tt>=hh&&max1[q[tt]][id]<=max1[i][id])tt--;
q[++tt]=i;
if(i>=a)max2[i][id]=max1[q[hh]][id];
}
}
int main(){
scanf("%d%d%d%d",&n,&m,&a,&b);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)scanf("%d",&g[i][j]);
for(int i=1;i<=n;i++) getmin1(i);
for(int i=b;i<=m;i++)getmin2(i);
for(int i=1;i<=n;i++)getmax1(i);
for(int i=b;i<=m;i++)getmax2(i);
long long res(0);
for(int i=a;i<=n;i++)
for(int j=b;j<=m;j++) res+=((long long)min2[i][j]*max2[i][j])%mod,res%=mod;
printf("%d\n",res);
return 0;
}