poj2823 Sliding Window

Sliding Window
Time Limit: 12000MS   Memory Limit: 65536K
Total Submissions: 52857   Accepted: 15140
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 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

Source

Analysis

题目大意是说,给你一个长度为n的数组,一扇长度为k的“滑动的窗口”,窗口恰好能容纳k个数,窗口从数组的最左端滑到最右端,每次向右滑动一个单位,问你,每到一个位置,最大值和最小值分别是多少,依次输出。

顺便用这个题入门单调队列了

法一:
每次移动一个单位,花O(k)的时间重新比较,这样的复杂度是(N-k)*k,即-k^2+Nk,抛物线在k∈[1,10^6]内,可得最大值(n^2)/4,超时
法二:
维护两棵平衡树,每次移动,就分别往两棵平衡树中插入节点,并查询最小、最大值,复杂度O(nlogn),不超时,但不好写
法三:
维护两个堆,一个大根堆、一个小根堆,剩下的就不说了,复杂度O(nlogn)。这个方法可以用优先队列降低编程复杂度,可行
法四:
用单调队列,复杂度O(n),编程复杂度较低。

我的探索历程:
单调队列我只是看了一下大体介绍,最后自己推出来的,这样理解的比较好吧,我觉得

对于这道题,我们先只考虑找最小值的情况
我们维护一个单调递增的数组q,将原先的数列a从1到n扫描,到i位置时,把a[i]这个元素通过类似插入排序的方式查到它的位置,插入,其他元素后移。同时把a[i-k]这个元素找到删掉,后面的元素前移。那么每到达一个大于等于k的位置,q[1]就是这个位置对应的最小值。复杂度为O(N^2)

这个算法的时间主要浪费在查找和挪动数据上了,观察一个数a[i],设它为t,设它在q中插入后的位置是j。
先来看泛化的情况,假如t插入到了q的中间某个位置,那么q被分成q[1..j-1]、q[j]和q[j+1..i],另S=这时的q[j+1..i]
后来的操作无非就是插入删除单个元素,整体挪动。在q[j]被删除之前,S中的元素一定不会跑到q[j]前面去,
因此假设q[j]后来变成了第一个元素,那么S中的元素一定在它之后,不会被查询到。如果q[j]不存在了,被删了,那么因为S中的元素在a中的下标比q[j]小,所以这时S中的元素也全没了
总结为一句话:如果q[j]存在,那么S中的元素不会被查询到,如果q[j]不存在,S也已经不存在
q[j]一旦插入,q[j+1..i]的元素永远没有用了
于是,我们就可以在q[j]插入的时候,直接把它后面的全删掉
那么插入操作变成:不断删除q末尾的元素,直到最后一个元素比待插入元素小
再来关心删除操作:每次窗口挪动一下,就有个元素要删除,假设它是t,如果t存在,说明t小于当前q中的所有元素(否则会被挤掉),所以只要看一看q中第一个元素的下表是不是和待删元素一样就行了,一样就删掉,不一样就不管

时间复杂度:
明显地,每一个元素至多进队一次,至多出队一次,因此每个元素最多被操作2次,得时间复杂度O(n)

我一直不太赞成单调队列这种叫法,因为你可以同时在队头队尾任意地插入删除,这已经不是先进先出了,这就是一个“只能在两头进行插入删除操作的线性表”

Code

//Poj2823 Slinding Window 单调队列
#include <cstdio>
#define maxn 2000000
using namespace std;

struct X
{
	int v, p;
}q1[maxn], q2[maxn];
int n, k, l1=1, r1=0, l2=1, r2=0, ans[5][maxn];

int main()
{
	int i;
	X a;
	
	scanf("%d%d",&n,&k);
	
	for(i=1;i<=n;i++)
	{
		scanf("%d",&a.v);
		a.p=i;
		
		while(a.v<=q1[r1].v&&r1>=l1)r1--;
		q1[++r1]=a;
		if(q1[l1].p==i-k)l1++;
		ans[1][i]=q1[l1].v;
		
		while(a.v>=q2[r2].v&&r2>=l2)r2--;
		q2[++r2]=a;
		if(q2[l2].p==i-k)l2++;
		ans[2][i]=q2[l2].v;
	}
	printf("%d",ans[1][k]);
	for(i=k+1;i<=n;i++)printf(" %d",ans[1][i]);
	printf("\n%d",ans[2][k]);
	for(i=k+1;i<=n;i++)printf(" %d",ans[2][i]);
	printf("\n");
	return 0;
}





















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值