单调队列-滑动窗口
此题就是单调队列的经典应用,暴力做法就是我们每次维护一个队列(窗口),然后从头遍历队列,然后找到最小(大)值,然后队列继续往后走。
这样的话时间复杂度是很高的,我们可以向单调栈那样考虑有没有那些元素时永远不会作为答案输出的。
下面我们以求窗口中的最小值为例进行分析
当队列中先进来的元素比后进来的元素大的话,那么这个先进来的元素是永远不会作为答案的,因为队列是先进先出。
举个例子 比如一个窗口中 0 2 -1 (0先进队),那么0和2当-1还在窗口中时永远不会作为答案输出,(因为0先进,移动窗口时0先出,然后是2. . .)
所以,我们就可以在遍历的时候,每次都与队尾元素进行比较,当新元素小于队尾元素时,那么说明队尾元素永远不会被用到,就删除队尾元素,一直到队尾的元素比当前元素小时停止,这样保证了队列中始终时单调递增的,那么每次从窗口中直接去队头元素就是答案。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int start = 0, end = 0;
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int k = scanner.nextInt();
int a[] = new int[n];
//模拟队列 存储的是a数组的索引 方便判断窗口的移动
int q[] = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
for (int i = 0; i < n; i++) {
//判断当队列不为空时且队头元素已经超出了窗口的最左边,就将队头出队
if (start != end && i - k + 1 > q[start]) start++;
//这里的end是从0开始的,end总是指向待入队元素的位置 所以end-1才是队尾元素
while (start != end && a[i] <= a[q[end - 1]]) end--;
//最后无论如何将新来索引入队
q[end++] = i;
//这里是判断是够形成了窗口 因为刚开始时可能没有形成窗口
if (i >= k - 1) System.out.print(a[q[start]] + " ");
}
//找窗口中的最大值
System.out.println();
start = 0;
end = 0;
for (int i = 0; i < n; i++) {
if (start != end && i - k + 1 > q[start]) start++;
while (start != end && a[i] >= a[q[end - 1]]) end--;
q[end++] = i;
//这一步就是判断当窗口开始形成时才开始输出 i=k-1
if (i >= k - 1) System.out.print(a[q[start]] + " ");
}
}
}