本题大意是给出一个长度为 n 的数组,编程输出每 k 个连续的数中的最大值和最小值。
使用单调队列可将复杂度优化到O(n)
例如[3,2,2,1,4,2]的最大值,始终把最大值放到头部位置
首先3进队列;q={3}
然后2,因为q最后一个大于2,2进队列.q={3,2}
然后2,因为q最后一个等于2,要弹出队尾,q={3},3比2大,2进,q={3,2}
然后1,q={3,2,1}
然后4,4比这几个数字都要大,弹出q={4}
while (head <= tail && a[q[tail]] <= a[i]) tail--;
q[++tail] = i;
head<=tail 表示队列有元素 a[q[tail]]>=a[i]表示队尾元素小于新的
这个循环让队列依次弹出1 2 3然后4入队
这样我们就可以使得k区间的最大值 始终是a[q[head]]
具体的操作是:
定义一个数组a[N]:用来存放原始数据
定义一个数组q[n]:表示队列.(deque)
获取最小值:
//代表q没有元素
int head = 0, tail = -1;
//先形成一个长度为(k-1)的窗口
for (int i = 1; i < k; i++) {
//存在元素 尾巴元素大于新来的 弹出尾巴
while (head <= tail && a[q[tail]] >= a[i]) tail--;
//入队(切记入队的是数组下标)
q[++tail] = i;
}
解释一下为什么要先形成一个(k-1)的窗口:
对于这个序列,很明显是求划线区间的最小值
像下面一样形成(k-1)的窗口 就可以做到窗口向右滑 得到一个答案 窗口向右滑 又得到一个答案
形成这个窗口后:
//刚才形成(k-1)的窗口 现在来到k位置
for (int i = k; i <= n; i++) {
//存在元素 尾巴元素大于新来的 弹出尾巴
while (head <= tail && a[q[tail]] >= a[i]) tail--;
//入队(切记入队的是数组下标)
q[++tail] = i;
//因为q[head]表示下标
while (q[head] <= i - k) head++;
cout << a[q[head]] << " ";
}
完整代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 1E6 + 10;
//注意q数组存的是下标
int q[N];
int a[N];
int n, k;
void getmin() {
//代表q没有元素
int head = 0, tail = -1;
//先形成一个长度为(k-1)的窗口
for (int i = 1; i < k; i++) {
//存在元素 尾巴元素大于新来的 弹出尾巴
while (head <= tail && a[q[tail]] >= a[i]) tail--;
//入队(切记入队的是数组下标)
q[++tail] = i;
}
//刚才形成(k-1)的窗口 现在来到k位置
for (int i = k; i <= n; i++) {
//存在元素 尾巴元素大于新来的 弹出尾巴
while (head <= tail && a[q[tail]] >= a[i]) tail--;
//入队(切记入队的是数组下标)
q[++tail] = i;
//因为q[head]表示下标
while (q[head] <= i - k) head++;
cout << a[q[head]] << " ";
}
}
void getmax() {
int head = 0, tail = -1;
for (int i = 1; i < k; i++) {
while (head <= tail && a[q[tail]] <= a[i]) tail--;
q[++tail] = i;
}
for (int i = k; i <= n; i++) {
while (head <= tail && a[q[tail]] <= a[i]) tail--;
q[++tail] = i;
while (q[head] <= i - k) head++;
cout << a[q[head]] << " ";
}
}
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)cin >> a[i];
getmin();
cout << endl;
getmax();
cout << endl;
return 0;
}