题目地址:
https://www.lintcode.com/problem/sliding-window-maximum/description
给定一个数组,再给定一个正整数 k k k,求数组所有长度为 k k k的滑动窗口最大数字。
思路是单调队列。维护一个单调下降的双端队列(从队头到队尾严格下降),每遇到一个数 x x x的时候,先将队尾小于等于 x x x的数poll出来,再将队头出了滑动窗口的数也poll出来,再把 x x x加入队列。这样队头维护的就是滑动窗口的最大值。注意,由于要将队头出了滑动窗口的数也poll出来,如果队列存的是数组中的数字本身的话,会很不方便,所以队列存的实际上是数的下标。代码如下:
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
public class Solution {
/**
* @param nums: A list of integers.
* @param k: An integer
* @return: The maximum number inside the window at each moving.
*/
public List<Integer> maxSlidingWindow(int[] nums, int k) {
// write your code here
List<Integer> res = new ArrayList<>();
Deque<Integer> deque = new ArrayDeque<>();
// 我们计算以nums[i]为最后元素的滑动窗口中的最大数
for (int i = 0; i < nums.length; i++) {
// 将队尾小于等于nums[i]的下标出队
while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
deque.pollLast();
}
// 如果队首下标出了窗口,也出队
if (!deque.isEmpty() && deque.peekFirst() <= i - k) {
deque.pollFirst();
}
// 最后再将当前数字下标入队
deque.offerLast(i);
if (i >= k - 1) {
// 队头存的是滑动窗口最大数的下标,加入最终结果res
res.add(nums[deque.peekFirst()]);
}
}
return res;
}
}
时间复杂度 O ( n ) O(n) O(n),空间 O ( k ) O(k) O(k)。
算法正确性证明:
首先证明队列中数字保持单调。对
i
i
i进行数学归纳法。当
i
=
0
i=0
i=0时循环结束时队列里只有一个数,显然成立;假设对
i
=
k
i=k
i=k时成立,当
i
=
k
+
1
i=k+1
i=k+1时,由于队列已经单调,所以小于
A
[
i
]
A[i]
A[i]的数都集中在队尾,while循环中队尾大于
A
[
i
]
A[i]
A[i]的数(下标)都会出队,所以队列维持单调不变。
接下来证明,对于每个 i i i,队列里从队头到队尾存放的是从 A [ i − k + 1 , . . . , i ] A[i-k+1,...,i] A[i−k+1,...,i]的数中,最大数在队头,最大数右边的数中的最大数,在队列第二位,此数右边的数中的最大数,在队列第三位,以此类推。也可以用数学归纳法来证明。由于每当新来一个数,都会把队尾不超过它的数PK下来,并且把出了窗口的数也出队,所以这一条结论显然成立。那么队头就维护了窗口最大值。所以算法正确。