POJ 2823 Sliding Window(优先队列)

Sliding Window
Time Limit: 12000MS Memory Limit: 65536K
Total Submissions: 54066 Accepted: 15515
Case Time Limit: 5000MS

Description

An array of size  n ≤ 10 6 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 positionMinimum valueMaximum value
[1  3  -1] -3  5  3  6  7 -13
 1 [3  -1  -3] 5  3  6  7 -33
 1  3 [-1  -3  5] 3  6  7 -35
 1  3  -1 [-3  5  3] 6  7 -35
 1  3  -1  -3 [5  3  6] 7 36
 1  3  -1  -3  5 [3  6  7]37

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

Source



思路:
题意就是说,在一串数字里面,有一个固定大小的滑动窗口,每次窗口都只能往后移动一个格,让你求每次移动后窗口中所有数的最大值和最小值,然后并且按题干中的格式输出来。一开始的思路是,由于是按顺序输出并且是两行分别输出,所以要用数组来保存结果然后最后再一起输出。对于每次移动后的窗口,因为每次如果都用for循环来寻找这几个数里面的最大值和最小值的话,那要for n-k+1次,肯定会超时。所以这时候我们就要想到用优先队列的知识,可以用一个最大优先队列和一个最小优先队列,因为每次进入都有优先级,所以每次进入的第一个就是这k个数里面的最大值或者最小值。然后每次都push进去这一个数,等到最后所有的数都已经进入了队列,再一个一个地top、pop出来就可以了。但是这样的思路是有猫病的,就比如你每次pop出来的数,如果它不是应该pop出来的数呢?就比如它是第三个数,虽然是第一大的数,但是要pop出来的应该是第一个数,而不是第一大的数,所以此种做法是行不通的。
再换种思路,由于我们这里要考虑的因素有两个,一个是入队的时间,另一个就是这个数值的大小。所以如果仅仅考虑一个要素来 优先进队的话会没办法再对已经进队的元素进行操作。就比如你队尾的一个元素怎么都不会用到而且会被删去,你没有办法把它一直挪到队首来pop掉,所以我们只能以优先队列的思想,自己写操作而不是用STL里面的top、pop等函数来实现。

现在就以求最大值为例:对于插入操作,由于从队头到队尾,元素对应的时刻(此题中是该元素在数列a中的下标)是递增的,但不要求连续,(这是为了保证最左面的元素总是最先过期,且每当有新元素来临的时候一定是插入队尾),因此来的新元素插入到队列的最后就能仍然满足时间是递增的。但是为了仍然满足数值也是递减的的成立,从队尾插入新元素的时候可能要删除队尾的一些元素,具体说来就是,找到第一个大于新元素的元素,删除其后所有元素,并将新元素插于其后。因为所有被删除的元素都比新元素要小,而且比新元素要旧,因此在以后的任何查询中都不可能成为答案,所以可以放心删除。对于查询操作,由于时间是递增的,因此所有该时刻过期的元素一定都集中在队头,因此利用查询的时机删除队头所有过期的元素,在不含过期元素后,队头得元素就是查询的答案,将其返回即可。由于每个元素都进队出队一次,因此摊销复杂度为O(n)。


代码:
//第一种方法,非STL方法
#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

const int N=1000010;

int n,k,num[N];

struct Que{
    int loc,x;
    Que(){}
    Que(int _loc,int _x):loc(_loc),x(_x){}
}minQ[N],maxQ[N];   //一个最小队列,一个最大队列

int minl,minr,maxl,maxr;

int main(){

    //freopen("input.txt","r",stdin);

    while(~scanf("%d%d",&n,&k)){
        for(int i=0;i<n;i++)
            scanf("%d",&num[i]);

        minl=minr=0;
        int i;
        for(i=0;i<k;i++){   //队列初始化
            while(minr>0 && minQ[minr-1].x>num[i])
                minr--;
            minQ[minr++]=Que(i,num[i]);
        }
        printf("%d",minQ[minl].x);
        for(;i<n;i++){
            if(minQ[minl].loc<i-k+1)    //判断队首元素是否还在查询范围之内
                minl++;
            while(minr>minl && minQ[minr-1].x>num[i])   //循环比较队尾元素与插入元素的大小,直到对尾元素<=插入元素或者队为空
                minr--;
            minQ[minr++]=Que(i,num[i]);     //入队
            printf(" %d",minQ[minl].x);
        }
        printf("\n");

        maxl=maxr=0;    //与上面求最小值相同
        for(i=0;i<k;i++){
            while(maxr>0 && maxQ[maxr-1].x<num[i])
                maxr--;
            maxQ[maxr++]=Que(i,num[i]);
        }
        printf("%d",maxQ[maxl].x);
        for(;i<n;i++){
            if(maxQ[maxl].loc<i-k+1)
                maxl++;
            while(maxr>maxl && maxQ[maxr-1].x<num[i])
                maxr--;
            maxQ[maxr++]=Que(i,num[i]);
            printf(" %d",maxQ[maxl].x);
        }
        printf("\n");
    }
    return 0;
}

//第二种方法 STL方法
#include<algorithm>
#include<queue>
#include<cstdio>
#include<vector>
using namespace std;
int a[1000011];//数组数据

int OutMin[1000011];//最小值

int OutMax[1000011];//最大值

int cnt1=0;
int cnt2=0;
int n,k;

struct cmp1
{
    bool operator()(const int a1,const int a2)
    {
        return a[a1]>a[a2];
    }
};
struct cmp2
{
    bool operator()(const int a1,const int a2)
    {
        return a[a1]<a[a2];
    }
};
priority_queue <int ,vector<int>,cmp1> Q1;//最大优先 
priority_queue <int ,vector<int>,cmp2> Q2;//最小优先 
int main()
{
    int i;
    scanf("%d%d",&n,&k);
    if(k>n)
        k=n;
    for(i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
    }
    for(i=1;i<=k;++i)
    {
        Q1.push(i);//按数值大小入队,入队的是时间
        Q2.push(i);
    }
    OutMin[cnt1++]=a[Q1.top()];//把队首元素提取出来
    OutMax[cnt2++]=a[Q2.top()];
    for(i=k+1;i<=n;++i)//向后移动
    {
        Q1.push(i);
        Q2.push(i);
        while(i-Q1.top()>=k)//如果队首元素过期了,就删除队首元素
            Q1.pop();
        OutMin[cnt1++]=a[Q1.top()];//储存删除之后的队首元素
        while(i-Q2.top()>=k)
            Q2.pop();
        OutMax[cnt2++]=a[Q2.top()];
    }
    
    for(i=0;i<=(n-k);++i)
    {
        printf("%d%c", OutMin[i], (i < n - k) ? ' ' : '\n'); 
        
    }
    for(i=0;i<=(n-k);++i)
    {
        printf("%d%c", OutMax[i], (i < n - k) ? ' ' : '\n'); 
    }
    return 0;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值