题目信息

  • 题目:剑指 Offer 59 - I. 滑动窗口的最大值

  • 时间: 2020-08-10

  • 题目链接:Leetcode

  • tag: 队列 双端队列 滑动窗口

  • 难易程度:困难

  • 题目描述:

    给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:

滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

注意

1
1. 你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

解题思路

本题难点

数组无序,窗口内的最大值。

具体思路

双向队列:在头部进行插入、删除操作,也能在尾部进行插入删除操作。

单调的双向队列:从队列的头部到尾部,所存储的元素是依次递减(或依次递增)的。

我们维护一个单调的双向队列,窗口在每次滑动的时候,我就从队列头部取当前窗口中的最大值,每次窗口新进来一个元素的时候,我就将它与队列中的元素进行大小比较:

  • 进来的元素 > 队列的尾部元素:那么先将队列尾部的元素弹出,然后把刚刚进来的元素添到队列的尾部;
  • 进来的元素 < 队列的尾部元素:那么把刚刚进来的元素直接添到队列的尾部即可。

删除元素:当队列的大小超过窗口的大小时,将队列的最大值弹出,窗口进行滑动。

代码

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
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums == null || k == 0){
return new int[0];
}
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
int count = 0;
for(int i = 0; i < nums.length; i++){
// 在队列不为空的情况下,如果队列尾部的元素要比当前的元素小,或等于当前的元素
// 那么为了维持从大到小的原则,我必须让尾部元素弹出
while(!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]){
deque.pollLast();
}
// 不走 while 的话,说明我们正常在队列尾部添加元素
deque.add(i);
// 如果滑动窗口已经略过了队列中头部的元素,则将头部元素弹出
if(deque.peekFirst() == (i - k)){
deque.pollFirst();
}
// 看看窗口有没有形成,只有形成了大小为 k 的窗口,我才能收集窗口内的最大值
if(i >= k-1){
res[count++] = nums[deque.peekFirst()];
}
}
return res;
}
}

复杂度分析:

  • 时间复杂度 O(N) : 其中 n 为数组 nums 长度;线性遍历 nums 占用 O(N) ;每个元素最多仅入队和出队一次,因此单调队列 deque 占用 O(2N) 。
  • 空间复杂度 O(k) : 双端队列 deque 中最多同时存储 k个元素(即窗口大小)。

其他优秀解答

解题思路

改善之后的暴力法

代码

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
31
32
33
34
public int[] maxSlidingWindow(int[] nums, int k) {
int len = nums.length;
if (len == 0){
return new int[0];
}
//定义结果数组
int[] res = new int[len - k + 1];
//maxInd记录每次最大值的下标,max记录最大值
int maxInd = -1, max = Integer.MIN_VALUE;

for (int i = 0; i < len - k + 1; i++) {
//判断最大值下标是否在滑动窗口的范围内
if (maxInd >= i){
//存在就只需要比较最后面的值是否大于上一个窗口最大值
if (nums[i + k - 1] > max){
max = nums[i + k - 1];
//更新最大值下标
maxInd = i + k - 1;
}
}
//如果不在就重新寻找当前窗口最大值
else {
max = nums[i];
for (int j = i; j < i + k; j++) {
if (max < nums[j]) {
max = nums[j];
maxInd = j;
}
}
}
res[i] = max;
}
return res;
}