题目:
有一个整型数组 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]。
解答:
如果数组的长度为 N, 窗口的大小为 w,求一个时间复杂度 O(N)的解法。
本题的关键在于用双端队列来实现窗口最大值的更新,首先生成双端队列 qmax,qmax中放入数组 arr中的下标。
假设遍历到 arr[i],qmax的放入规则:
-
如果qmax 为空,直接把下标 i 放入 qmax,放入过程结束。
-
如果qmax 不为空,取出当前 qmax 队尾存放的下标,假设为 j。
1)如果 arr[j] > arr[i], 直接把下标 i 放进 qmax 的队尾,放入过程结束。
2)如果 arr[j] <= arr[i],把 j 从qmax 中弹出,继续 qmax 的放入规则
假设遍历到 arr[i], qmax的弹出规则为:
如果 qmax 队头的下标等于 i - w 表示当前 qmax 队头下标已经过期,弹出当前队头的下标即可。
根据如上的放入和弹出规则,qmax便成了一个维护窗口为w的最大值更新结构。下面以本题为例:
1.开始时 qmax 为空,qmax={}
2.遍历到arr[0] == 4,将下标 0 放入 qmax, qmax = {0}。
3.遍历到arr[1] == 3,当前qmax队尾下标为0,又有arr[0] > arr[1],所以将下标 1 放入 qmax 的尾部,qmax={0,1}。
4.遍历到arr[2] == 5,当前qmax的队尾的下标为 1,又有arr[1] <= arr[2]所以将 1 从队列中弹出,qmax 变为{0}。当前的 qmax 的队尾下标为 0 ,由于 arr[0] < arr[2],将当前下标 0 从qmax 尾部弹出,qmax 当前变为 {}。将 2 放入 qmax ,qmax = {2}。此时窗口 arr[0..2]出现了,当前 qmax 队头的下标为2,所有当前 arr[0..2]最大值为5。
5.遍历到 arr[3] == 4,当前 qmax的队尾下标为2,又有 arr[2] > arr[3],所以将 3 放入 qmax 尾部,qmax = {2,3}窗口{1..3}出现,当前 qmax 队头的下标为2,这个下标还没有过期,所以窗口arr[1..a]的最大值为arr[2]( 即5)
6.遍历到 arr[4] == 3,当前qmax下标为3,又有arr[3] > arr[4], 所以将下标4放入qmax尾部,qmax={2,3,4}窗口[2..4]出现,当前qmax队列的队头下标为2,这个下标还没有过期。
7.遍历到arr[5] == 3,当前qmax队尾下标为4,又有arr[4] <= arr[5],所以将下标4从qmax尾部弹出,qmax变为{2,3}。当前qmax的队尾变为3,又有arr[3]>arr[5],所以将5放入qmax尾部,qmax = {2,3,5}当前qmax队头下标为2,所以将2从头部弹出,qmax变为{3,5}当前qmax队头的下标为3这个下标没有过期所以窗口arr[3..5]的最大值为arr[3]即4。
8.遍历到arr[6] == 6,当前qmax队尾的下标变为5,又有arr[5]<=arr[6]所以将下标5从队列尾部弹出,qmax变为{3}。当前qmax的队尾下标为3,又有arr[3] <= arr[6],所以将下标3从qmax尾部弹出,qmax变为{}。将下标6放入qmax。
窗口[4..6]出现了(即6)。
9.遍历到arr[7] == 7,当前qmax的队尾下标为6,又有arr[6]<=arr[7],所以将下标6从qmax尾部弹出将7放入qmax,qmax={7}。窗口arr[5..7]出现,当前qmax队头下标为7,这个下标没有过期,所以窗口arr[5..7]最大值为arr[7](即7)。
10.依次将出现的窗口最大值[5,5,5,4,6,7],在遍历的时候收集起来,最后返回即可。
上述过程中,每个下标值最多进qmax一次,出qmax一次。所以遍历的过程中进出双端队列的操作时间复杂度O(N),整体的时间复杂度O(N)
import java.util.LinkedList;
public class GetMaxWindows {
public int[] getMaxWindows(int[] arr, int w){
if (arr == null || w < 1 || arr.length < w){
return null;
}
LinkedList<Integer> qmax = new LinkedList<Integer>();
int[] res = new int[arr.length - w + 1];
int index = 0;
for(int i = 0; i < arr.length; i++){
while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]){
qmax.pollLast();
}
qmax.addLast(i);
//判断当前队列头部的坐标是否已经过期
if (qmax.peekFirst() == i - w){
qmax.pollFirst();
}
//将窗口最大值的下标存入 res 数组中
if(i >= w - 1){
res[index++] = arr[qmax.peekFirst()];
}
}
return res;
}
public static void main(String[] args) {
}
}