An array of size n ≤ 10 6 is given to you. There is a sliding window of size kwhich 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:
一个数量级小于一百万的数组已经给定了。有一个K个长度的滑动窗口从最左端滑到最右端。透过这个窗口你只能看见K个数字。每一回滑动都是向右移动一个单位,例子如下:
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.
你的任务是决定滑动窗口在每个位置的最大最小值。
本题是单调队列的教学题,其实可以算成双端队列的应用。如果用C++需要个人数组模拟,但是JAVA有双倍时间,无惧这些。
单调队列是一种特殊的队列,队列内的元素要保证一定的单调性,或是递增或是递减,或是非递减或是非递增。如果你想保持队列内单调性一致的话,就需要在插入元素前将队列尾部元素和待插入元素比对,如果后者破坏了单调性就依据需要决定这个元素是否需要加入或者队列尾部元素是否需要驱逐,如果没有破坏单调性,就直接插入即可,以上操作与单调栈相关操作一致。
不过一般都需要单调队列的容量保持一定的大小,所以还得定期处理队列头部的元素。比如当前这个题,你就需要在某个元素不存在于队列之内的时候将之剔除,判断的标准就是用下标。如果队首元素的下标等于i-k(队列大小)+1,那么就说明这个元素已经在这个滑动窗口的边缘了,下一步就出界,所以要在当前步骤中将之弹出。
因为前后都有删除元素的操作,所以一般的队列是无法实现的,这便是双端队列的用武之地。
值得注意的是,单调队列维护的最值都会在队首出现。
如果用过程模拟一下样例(1 3 -1 -3 5 3 6 7)的最小值求解(队列为单增队列),就是如下的一系列操作。
- 队列中没有元素,元素1入队列
- 元素3大于队尾元素1,元素3入队列
- 元素-1小于队尾元素3,将其弹出,小于队尾元素1,将其弹出,-1入队列。
- 此时下标已经到达K(滑动窗口的起始位置),可以记录最小值,第一个最小值就是-1
- -3小于队尾元素-1,将其弹出,-3入队列,记录最小值-3
- 5大于队尾元素-3,5入队列,记录最小值-3
- 3小于队尾元素5,将5弹出,3入队列,记录最小值-3
- 6大于队尾元素3,6入队列,此时-3已经不在滑动窗口里(i-k+1范围),将其弹出,记录最小值3
- 7大于队尾元素6,7入队列,记录最小值3
- 程序终止。
import java.io.*;
import java.util.*;
import com.sun.jmx.remote.internal.ArrayQueue;
import java.math.*;
import java.text.*;
public class Main36
{
public static class Element
{
int value,index;
public Element(int a,int b)
{
this.value=a;
this.index=b;
}
}
public static void main(String args[])throws IOException
{
StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
while(st.nextToken()!=StreamTokenizer.TT_EOF)
{
int n=(int)st.nval;
st.nextToken();
int k=(int)st.nval;
Element elements[]=new Element[n+199];
Deque <Element> q=new ArrayDeque<Element>();
Deque <Element> q2=new ArrayDeque<Element>();
int MinAns[]=new int[n+199];
int MaxAns[]=new int[n+199];
for(int i=1;i<=n;i++)
{
st.nextToken();
elements[i]=new Element((int)st.nval,i);
}
int cnt1=0,cnt2=0;
for(int i=1;i<=n;i++)
{
while(q.size()>0&&q.getLast().value>=elements[i].value)
q.removeLast();
q.addLast(elements[i]);
if(i>=k)
MinAns[++cnt1]=q.getFirst().value;
if(q.getFirst().index==i-k+1)
q.removeFirst();
while(q2.size()>0&&q2.getLast().value<=elements[i].value)
q2.removeLast();
q2.addLast(elements[i]);
if(i>=k)
MaxAns[++cnt2]=q2.getFirst().value;
if(q2.getFirst().index==i-k+1)
q2.removeFirst();
}
for(int i=1;i<=cnt1;i++)
pw.print(MinAns[i]+" ");
pw.println("");
for(int i=1;i<=cnt2;i++)
pw.print(MaxAns[i]+" ");
pw.println("");
pw.flush();
}
pw.close();
}
}