单调队列(滑动窗口问题)(待续...)

Description

An array of size n ≤ 106 is given to you. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves rightwards by one position. Following is an example:
The array is [1 3 -1 -3 5 3 6 7], and k is 3.

Window position

Minimum value

Maximum value

[1  3  -1] -3  5  3  6  7 

-1

3

 1 [3  -1  -3] 5  3  6  7 

-3

3

 1  3 [-1  -3  5] 3  6  7 

-3

5

 1  3  -1 [-3  5  3] 6  7 

-3

5

 1  3  -1  -3 [5  3  6] 7 

3

6

 1  3  -1  -3  5 [3  6  7]

3

7

Your task is to determine the maximum and minimum values in the sliding window at each position.

Input

The input consists of two lines. The first line contains two integers n and k which are the lengths of the array and the sliding window. There are n integers in the second line.

Output

There are two lines in the output. The first line gives the minimum values in the window at each position, from left to right, respectively. The second line gives the maximum values.

Sample Input

8 3

1 3 -1 -3 5 3 6 7

Sample Output

-1 -3 -3 -3 3 3

3 3 5 5 6 7

 

方法一:优先队列

  使用STL中的优先队列priority_queue进行求解。开两个优先队列(求max与min),优先队列中插入的是元素的下标,队列中按元素值的从大到小和从小到大优先放在上面。每插入一个元素后,将队列中已经跑出窗口外的元素删除,队列的top元素所指的元素就是该窗口内的最大或最小值。

参考http://hi.baidu.com/xiaohanhoho/blog/item/b020b923aac13cfad6cae228.html

 

/*优先队列(STL)*/
#include <iostream>
#include <vector>
#include <queue>
#define MAXN 1000005
using namespace std;
int S[MAXN];//存数组数据
int Min[MAXN],Max[MAXN];//存最小值和最大值
int cnt;
struct cmp1//最小优先队列,结构体+重载运算符,注意掌握写法
{
    bool operator()(int s1,int s2)
    {
        return S[s1]>S[s2]; //元素升序
    }
};
struct cmp2//最大优先队列
{
    bool operator()(int s1,int s2)
    {
        return S[s1]<S[s2];//元素降序
    }
};
priority_queue<int,vector<int>,cmp1>Qmin;//自定义优先队列
priority_queue<int,vector<int>,cmp2>Qmax;
int main()
{
    int N,K,i;
    while(scanf("%d%d",&N,&K)!=EOF)
    {
        for(i=1; i<=N; i++)
            scanf("%d",&S[i]);
        for(i=1; i<=K; i++) //先把第一次查找的前K个插入队列
        {
            Qmin.push(i);
            Qmax.push(i);
        }
        cnt=1;
        Min[cnt]=S[Qmin.top()];//注意,队列内存的是原始数组的下标
        Max[cnt]=S[Qmax.top()];
        cnt++;
        for(i=K+1; i<=N; i++,cnt++)
        {
            Qmin.push(i);
            Qmax.push(i);
            while(i-Qmin.top()>=K)//删除已经移除届的数 注意此处理解:Qmin.top()为元素最小值的下标,若i与最小值下标间相差大于等于K,即最小值元素已删除,此时应Qmin.pop();
                Qmin.pop();
            Min[cnt]=S[Qmin.top()];
            while(i-Qmax.top()>=K)//删除已经移除届的数
                Qmax.pop();
            Max[cnt]=S[Qmax.top()];
        }
        for(i=1; i<cnt; i++)
            printf("%d ",Min[i]);
        printf("
");
        for(i=1; i<cnt; i++)
            printf("%d ",Max[i]);
        printf("
");
    }
    return 0;
}
View Code

 

方法二:单调队列(***)

什么是单调队列? 

转自:

 a)从队头到队尾,元素在我们所关注的指标下是递减的(严格递减,而不是非递增),比如查询如果每次问的是窗口内的最小值,那么队列中元素从左至右就应该递增(抛弃无用元素),如果每次问的是窗口内的最大值,则应该递减,依此类推。这是为了保证每次查询只需要取队头元素。

 b)从队头到队尾,元素对应的时刻(此题中是该元素在数列a中的下标)是递增的,但不要求连续,这是为了保证最左面的元素总是最先过期,且每当有新元素来临的时候一定是插入队尾。

 满足以上两点的队列就是单调队列,首先,只有第一个元素的序列一定是单调队列。

 那么怎么维护这个单调队列呢?无非是处理插入和查询两个操作。

 对于插入,由于性质b,因此来的新元素插入到队列的最后就能维持b)继续成立。但是为了维护a)的成立,即元素在我们关注的指标下递减,从队尾插入新元素的时候可能要删除队尾的一些元素,具体说来就是,找到第一个大于(在所关注指标下)新元素的元素,删除其后所有元素,并将新元素插于其后。因为所有被删除的元素都比新元素要小,而且比新元素要旧,因此在以后的任何查询中都不可能成为答案,所以可以放心删除。

 对于查询,由于性质b,因此所有该时刻过期的元素一定都集中在队头,因此利用查询的时机删除队头所有过期的元素,在不含过期元素后,队头得元素就是查询的答案(性质a),将其返回即可。

 由于每个元素都进队出队一次,因此摊销复杂度为O(n)。

 

  1 #include <iostream>
  2 #include <fstream>
  3 
  4 using namespace std;
  5 
  6 #define MAX 1000001
  7 int A[MAX]; //存储数据
  8 int Q[MAX]; //队列
  9 int P[MAX]; //存储A[i]中的下标i
 10 int Min[MAX];  //输出最小
 11 int Max[MAX];  //输出最大
 12 int n,k;
 13 
 14 void get_min()
 15 {
 16     int i;
 17     int head=1,tail=0;
 18     for(i=0; i<k-1; i++)  //先把前两个入队
 19     {
 20         while(head<=tail && Q[tail]>=A[i])  //队尾元素大于要插入的数
 21             --tail;
 22         Q[++tail]=A[i];
 23         P[tail]=i;
 24     }
 25 
 26     for(; i<n; i++)
 27     {
 28         while(head<=tail && Q[tail]>=A[i])
 29             --tail;
 30         Q[++tail]=A[i];
 31         P[tail]=i;
 32         while(P[head]<i-k+1)  //判断数是否过时,即窗口是否已经划过这个数,我这是从0开始计数的。
 33         {
 34             head++;
 35         }
 36         Min[i-k+1]=Q[head];
 37     }
 38 }
 39 
 40 void get_max()
 41 {
 42     int i;
 43     int head=1,tail=0;
 44     for(i=0; i<k-1; i++)
 45     {
 46         while(head<=tail && Q[tail]<=A[i])   //队尾元素小于要插入的值
 47             --tail;
 48         Q[++tail]=A[i];
 49         P[tail]=i;
 50     }
 51 
 52     for(; i<n; i++)
 53     {
 54 
 55         while(head<=tail && Q[tail]<=A[i])   //队尾元素小于要插入的值
 56             --tail;
 57         Q[++tail]=A[i];
 58         P[tail]=i;
 59         while(P[head]<i-k+1)
 60             {
 61             head++;
 62         }
 63         Max[i-k+1]=Q[head];
 64     }
 65 }
 66 
 67 void output()
 68 {
 69        int i;
 70       //输出最下值
 71        for(i=0; i<n-k+1; i++)
 72        {
 73            if(i==0)
 74                printf("%d",Min[i]);
 75            else
 76                printf(" %d",Min[i]);
 77        }
 78        printf("
");
 79        //输出最大值
 80        for(i=0; i<n-k+1; i++)
 81        {
 82            if(i==0)
 83                printf("%d",Max[i]);
 84            else
 85                printf(" %d",Max[i]);
 86        }
 87        printf("
");
 88 }
 89 
 90 
 91 int main()
 92 {
 93     int i;
 94     freopen("acm.txt","r",stdin);
 95     scanf("%d%d",&n,&k);
 96     for(i=0; i<n; i++)
 97     {
 98         scanf("%d",&A[i]);
 99     }
100     get_min();
101     get_max();
102     output();
103     return 0;
104 }
View Code

方法三:线段树(待续)

 

 

原文地址:https://www.cnblogs.com/LLGemini/p/3893074.html