单调队列

单调队列是指在任意时刻,队列中的元素都是单调的(递增或递减),同时他又具有双端队列的部分性质(允许从队尾删除元素)。

在这里,有一道经典的例题:滑动窗口求最值

题目描述:在一个长度为n的整数序列上有一个长度为k的滑动窗口,求滑动窗口内的最(大/小)值。

解释:就是在一个序列上对于每个长度为k的区间,求区间内的最值。

分析:一种朴素的做法是,枚举区间起点,再自此向后比较k个元素,找出最值,这样的复杂度是O(nk)的。

还有一种不错的做法是利用单调队列。不妨假设我们已经得到了一个单调队列,他维护了当前的滑动窗口,显然,

队首元素就是窗口内的最值。现在再来考虑如何用单调队列维护滑动窗口(以最大值为例,队列则为单调递减的,队首元素为窗口内的最大值):

我们遍历序列的每个元素,当队列为空时,肯定要加入队列;队列不为空,就要先从队尾弹出较小的元素,再加入,保证队列单调(这里有一个有趣的类比,如果一位OIer比你年轻还比你强,那你就没法超越他了);但滑动窗口是有长度限制的,怎么考虑呢?我们可以保存每个元素的序号,当发现队首元素的序号与当前考虑元素相比,已经出了滑动窗口,就弹出队首元素。

因为每个元素都只会进入队列一次且只会离开队列一次,可以认为时间复杂度是O(n)的。

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <deque>
 4 
 5 using namespace std;
 6 
 7 struct num { //定义结构体存储元素的序号及值
 8     int id, value;
 9 
10     num(int i, int v) : id(i), value(v) {}
11 };
12 
13 int n, k, a[10005], first = 1;
14 
15 deque<num> dq;
16 
17 int main() {
18     cin >> n >> k;
19     for(int i = 1; i <= n; ++i) {
20         scanf("%d", &a[i]);
21         if (dq.empty()) dq.push_back(num(i, a[i]));
22         //若队列为空,则直接进入队列
23         else {
24             num f = dq.front(); //取队首元素
25             if (i > f.id + k - 1) dq.pop_front();
26             //若队首元素的序号距当前元素太远(超出窗口长度)则弹出队首元素
27             num b = dq.back(); //取队尾元素
28             while (b.value < a[i]) { //若队尾元素小于当前元素则弹出队尾
29                 dq.pop_back();
30                 if (dq.empty()) break; //注意!队列为空则不能继续弹出
31                 b = dq.back();
32             }
33             dq.push_back(num(i, a[i])); //将当前元素放入队列中合适位置
34         }
35         if (i >= k) { //当考虑的元素个数达到窗口长度时,开始输出
36             if (first) first = 0;
37             else printf("\n");
38             num f = dq.front();
39             printf("第%d个滑动窗口的最大值为%d", i-k+1, f.value);
40         } //事实上,对于长度为n的序列,长度为k的窗口,共有n-k+1个不同的窗口
41     }
42     return 0;
43 }
滑动窗口

 

posted @ 2018-07-27 13:22  Mr^Kevin  阅读(251)  评论(2编辑  收藏  举报