题目所属分类
代码案例:
题解
需要判断队头元素是否还在窗口中,这一步需要用到下标。
所以队列里面存的是下标
这道题目,我们就维护两个队列,一个是最小值,一个是最大值.这里唯一的重点就是,每一次入队的时候,不需要管是不是比队头小,因为也许他现在小,但是在队头出队列后,他还在,而且是最小的值.
来自yxc老师
这道题目的时间限制卡得比较紧,需要用 O(n)
时间复杂度的算法来做。
这是一道单调队列的模板题,以求最小值为例:
我们从左到右扫描整个序列,用一个队列来维护最近 k
个元素;
如果用暴力来做,就是每次都遍历一遍队列中的所有元素,找出最小值即可,但这样时间复杂度就变成 O(nk) 了;
然后我们可以发现一个性质:
如果队列中存在两个元素,满足 a[i] >= a[j] 且 i < j,那么无论在什么时候我们都不会取 a[i] 作为最小值了,所以可以直接将 a[i] 删掉;
此时队列中剩下的元素严格单调递增,所以队头就是整个队列中的最小值,可以用 O(1) 的时间找到;
为了维护队列的这个性质,我们在往队尾插入元素之前,先将队尾大于等于当前数的元素全部弹出即可;
这样所有数均只进队一次,出队一次,所以时间复杂度是 O(n) 的。
import java.io.*;
public class Main{
static int N = 1000010;
static int n,k;
static int hh,tt;
static int[] q = new int[N];
static int[] a = new int[N];
public static void main(String[] args)throws IOException{
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String[] st = input.readLine().split(" ");
n = Integer.parseInt(st[0]);
k = Integer.parseInt(st[1]);
String[] str = input.readLine().split(" ");
for(int i = 0 ; i < n ; i ++ ) a[i] = Integer.parseInt(str[i]);
hh = 0 ; tt = -1;
for(int i = 0 ; i < n ; i++){
//判断队头是否已经出窗口 窗口最多往后移一位 所以这是if
if(hh <= tt && q[hh] < i - k + 1)hh++;
//插入的数在队尾插入 比前面的数小 那么队尾就减一
while(hh <= tt && a[q[tt]] >= a[i]) tt--;
//将当前数的下标插入到队列中(队列中存的是输入的数的下标)
q[++tt] = i ;//3
//i - k + 1是以i为右端点、长度为k的区间的左端点
//随着滑动窗口往右走
if( i >= k - 1)System.out.print(a[q[hh]] + " ");//4
//3和4是顺序不能改变 要先把i加进去,因为有可能输出的正是新加入的那个元素;
}
System.out.println();
hh = 0 ; tt = -1;
for(int i = 0 ; i < n ; i++){
if(hh <= tt && q[hh] < i - k + 1)hh++;
while(hh <= tt && a[q[tt]] <= a[i]) tt--;
q[++tt] = i ;
if( i >= k - 1)System.out.print(a[q[hh]] + " ");
}
}
}
注释解析版
import java.io.*;
public class Main{
public static void main(String[] args)throws IOException{
StreamTokenizer re = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
int N = 1000010;
int[] q = new int[N];//队列(存的是插入数的下标)
int[] a = new int[N];//输入的数
int hh = 0, tt = -1;
re.nextToken(); int n = (int)re.nval; // 输入n
//int n = scan.nextInt();
re.nextToken(); int k = (int)re.nval;//输入k
//int k = scan.nextInt();
for(int i = 0 ; i < n ; i ++ ){
re.nextToken(); a[i] = (int)re.nval; //输入所有的数;
//a[i] = scan.nextInt();
}
for(int i = 0 ; i < n ; i ++ ){
//判断队列是否是空(hh<=tt) 并 因为滑动窗口k的大小是规定好的,
//窗口向右移动,队头的下标要在窗口范围内才行,否则就把头去掉(hh++);
if(hh <= tt && q[hh] < i - k + 1) hh ++ ;
//判断队列是否是空的 并 因为求得窗口中的最小值,如果队尾元素大于我输入的数,
//那么就将队尾元素去掉 (tt--);
while(hh <= tt && a[q[tt]] >= a[i]) tt--;
//将当前数的下标插入到队列中(队列中存的是输入的数的下标)
q[++tt] = i;
//因为要确保插入数大于等于k这个滑动窗口的范围才能开始输出
if(i >= k - 1) System.out.print(a[q[hh]] + " ");
}
System.out.println();//换行
hh = 0; tt = -1;//重新赋值
for(int i = 0 ; i < n ; i ++ ){
//判断队列是否是空的
//因为滑动窗口k有范围限制,如果你的队头滑出窗口,就要把队头往后移动到下一位,将队头去掉
if(hh <= tt && q[hh] < i - k + 1) hh ++ ;
//判断队列是否是空的
//如果队尾元素小于等于我要插入的数,因为是要求滑动窗口中的最大值,
//所以前面比较小的数不会被用到,所以要删除,将队尾删除 (tt--);
while(hh <= tt && a[q[tt]] <= a[i]) tt--;
//将新插入的数的下标插入到队列中(队列中存的是输入的数的下标)
q[++tt] = i;
//因为滑动窗口有范围限制,所以要保证插入的数大于等于k是才开始输出
if(i >= k - 1) System.out.print(a[q[hh]] + " ");
}
}
}