题目分析
单调队列问题的经典例题,题意:有一串数,编号为 1- n , 问从 1 开始,求当前位置及其后共k个数中的最大值和最小值,每次位置向后移动一位,如图:
n = 8 , k = 3, v[] = { 1, 3 , -1 , -3 , 5 , 3 , 6 , 7 }
解题思路:
首先,这个题目肯定不能暴力求解,每个区间都找一次最大值和最小值。
然后,考虑到我们每次查询区间的位置都是向后移动一个位置,我们将这个移动后的区间最右边的数的去和移动前的区间的最大值和最小值去比较。
如果移动前的区间的最大值和最小值都不是移动前的区间的最左边的数的话,我们只需要比较移动前的区间中的最大值,最小值和移动后的区间最右边的数,就可以得到移动后的区间的最大值和最小值。
但是,如果移动前的区间中的最大值或者最小值就是移动前的区间的最左边的数的话,那么就需要重新寻找移动前的区间中第二大的数或者第二小的数。
综上,我们需要找到每个区间的最大值,次最大值,最小值,次最小值。
为此我们引入单调队列的方法:这个队列中的元素递增或者递减
维护方法:(单调递减队列)
1)我们插入元素的时候按照下标顺序插入
2)记新插入的数为 x , 从单调队列尾部开始,依次比较和 x 的值的大小,如果单调队列尾部的数 tail 比 x 小,那么就删除队列尾部这个数,重复之前的操作,直到遇到一个比 x 大的数,或者整个队列中的数都小于等于x , 那么队列就空了。最后再插入 x 。
3)经过第二步的操作,我们始终让队首的数为最大,队首后面的元素为次大,这样就可以满足我们需要一个区间中最大值和次最大值,而求最小值和次最小值的就用单调递增队列即可
4)那么,我们先将前k个数按上面的操作存入单调递增区间,这样我们就得到了区间 [ 1 , k ] 的最大值,次最大值,然后我们插入第 k+1 个数,重复上面的操作,这样就得到了区间 [ 1 , k+ 1 ] 的最大值 ,
5)但是我们需要求的区间是 [ 2, k+1] ,由于我们的队列中的排序规则为:大的数的靠前,数的大小相同的情况下,保留下标小的,大的剔除,这样一来,我们只需要看区间 [ 1, k +1 ] 队首元素的下标是否大于或等于需要求的区间的左边界即可,如果满足,那么这个队首就是在区间 [ 2 , k+1] 的最大值,否则,删除这个队首,直到队首的数的下标大于或等于当前区间的左边界的下标2
样例演示(求区间最大值,最小值就维护单调递增队列即可)
样例:n = 8 , k =3 , v[] = {1 , 3 , -1 , -3 , -5 , 5 , 3 , 7} (为了体现删除队首,更换了样例)
我们先构造一个长度为 k -1 = 2 的单调递减队列:
这样保证从插入第 i 个元素后( i >= k),我们队列中的队首元素就是第 i - k + 1 和区间的最大值
插入元素 1 , 队列:(1,1)
插入元素3 ,删除的队列中的比3小的元素,队列:(3,2)
插入元素-1,队列: (3,2)(-1,3)
插入元素-3,队列: (3,2)(-1,3)(-3,4)
插入元素-5,队列 : (-1,3)(-3,4)(-5,5); 之前的(3,2)已经不在当前区间内
插入元素5 , 队列: (5,6)
插入元素3 ,队列 : (5,6)( 3, 7)
插入元素7,队列: (7,8)
我们得到了我们需要的答案:3,3,-1,5,5,7
求区间最小值的操作和这个类似,只需要维护单调递增队列即可,其余操作一致
代码区
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include <vector>
using namespace std;
typedef long long ll;
const int Max = 2e6 + 10;
const int inf = 0x3f3f3f3f;
typedef struct Order {
int pos;
int val;
}Order;
Order order[Max];
int val[Max];
int min_sequence[Max], max_sequence[Max];//记录区间的最小值和最大值
int n, k;
int index;
void getMax()
{
index = 0; //代表前一个区间的左边界,同时用于记录当前所求区间的最值
int head = 0; //当前序列的头部
int tail = -1; //当前序列的尾部
for (int i = 1; i < k; i++)//先将最初的k-1个存入,保证从第 i 个元素插入后(i >= k),当前队列的队首元素为第 i - k +1个区间的最小值
{
while (head <= tail && order[tail].val <= val[i]) tail--;//保证单调递减,而且不能越界
order[++tail] = { i,val[i] };
}
for (int i = k ; i <= n; i++)//从第k个元素开始一个个地插入
{
while (head <= tail && order[tail].val <= val[i]) tail--;
order[++tail] = { i,val[i] };
while (order[head].pos <= index) head++; //不属于当前区间的最大值需要剔除
max_sequence[++index] = order[head].val; //当前队列的最大值就是第 i - k + 1的区间的最大值,这里用++index代替 i - k + 1
}
}
void getMin()
{
index = 0; //代表前一个区间的左边界,同时用于记录当前所求区间的最值
int head = 0; //当前序列的头部
int tail = -1; //当前序列的尾部
for (int i = 1; i < k; i++)//先将最初的k-1个存入,保证从第 i 个元素插入后(i >= k),当前队列的队首元素为第 i - k +1个区间的最大值
{
while (head <= tail && order[tail].val >= val[i]) tail--;//保证单调递增,而且不能越界
order[++tail] = { i,val[i] };
}
for (int i = k; i <= n; i++)//从第k个元素开始一个个地插入
{
while (head <= tail && order[tail].val >= val[i]) tail--;
order[++tail] = { i,val[i] };
while (order[head].pos <= index) head++; //不属于当前区间的最小值需要剔除
min_sequence[++index] = order[head].val; //当前队列的最小值就是第 i - k + 1的区间的最大值,这里用++index代替 i - k + 1
}
}
int main()
{
std::ios::sync_with_stdio(false);
while (cin >> n >> k)
{
for (int i = 1; i <= n; i++)
{
cin >> val[i];
}
getMin();
getMax();
for (int i = 1; i < index; i++)
{
cout << min_sequence[i] << " ";
}
cout<< min_sequence[index] << endl;
for(int i = 1; i < index; i ++)
{
cout << max_sequence[i] << " ";
}
cout << max_sequence[index] << endl;
}
return 0;
}