生成窗口最大值数组

有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。
例如,数组为[4,3,5,4,3,3,6,7],窗口大小为3时:

[4 3 5] 4 3 3 6 7 窗口中最大值为5
4 [3 5 4] 3 3 6 7 窗口中最大值为5
4 3 [5 4 3] 3 6 7 窗口中最大值为5
4 3 5 [4 3 3] 6 7 窗口中最大值为4
4 3 5 4 [3 3 6] 7 窗口中最大值为6
4 3 5 4 3 [3 6 7] 窗口中最大值为7

如果数组长度为n,窗口大小为w,则一共产生n-w+1个窗口的最大值。
请实现一个函数,给定一个数组arr,窗口大小w。
返回一个长度为n-w+1的数组res,res[i]表示每一种窗口状态下的最大值。
以本题为例,结果应该返回[5,5,5,4,6,7]。

思路

普通解法就是对于每个大小为w窗口都遍历,找到最大值,一共遍历n-w+1次,时间复杂度为O(w*n)
O(n)解法就是利用双端队列
双端队列中存放的是数组中的下标,表示的当前窗口中从大到小的最大值的下标
只需要遍历一次数组,对个数组中的每个值,如果比队列中末尾值小就放入队列,
否则就把队列中的最后一个值pop,直到可以放入为止。
然后更新结果res,队列中的第一个值是当前的最大值,但是要注意是否在窗口范围内,
如果不在,就pop,选择在窗口范围内的最大值

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static int[] getMaxWindow(int[] arr, int w) {
if (arr == null || w < 1 || arr.length < w) {
return null;
}
int[] res = new int[arr.length - w + 1];
//存放数组中数字的序号,且序号表示的数字顺序必须是从大到小
LinkedList<Integer> qMax = new LinkedList<Integer>();
//res的位置
int index = 0;
for(int i = 0; i < arr.length; i++){
//添加元素
//更新qmax的数据,对于满足顺序,即小于最后一个元素的直接放入,否则一直剔除最后一个,可能会使双端队列变空,直到满足要求
while(!qMax.isEmpty() && arr[qMax.getLast()] <= arr[i]){
qMax.removeLast();
}
qMax.add(i);
//更新res
if(i >= w - 1){
//由于qmax是按照从小到大的顺序排列,因此最大值一定是第一个,但是可能出现一个数不在当前窗口的范围之中
//检查qmax的数字是否有效,即在窗口之内
while(qMax.getFirst() < index || qMax.getFirst() > index + w){
qMax.removeFirst();
}
res[index++] = arr[qMax.getFirst()];
}
}
return res;
}