LeetCode239-滑动窗口最大值

题目链接

英文链接:https://leetcode.com/problems/sliding-window-maximum/

中文链接:https://leetcode-cn.com/problems/sliding-window-maximum/

题目详述

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 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

注意:

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

进阶:

你能在线性时间复杂度内解决此题吗?

题目详解

方法一:构建一个大顶堆,维护堆的大小为 k,时间复杂度为 O(nlogk)。代码略。

方法二:运用单调队列。时间复杂度为 O(n)。

  • 构建一个双端队列,队列里面元素从队首到队尾是严格按从大到小排序的。
  • 每加入一个元素之前,判断加入元素后队列是否满足单调性。
    • 若满足直接加入。
    • 若不满足,不断弹出队尾元素直至满足单调性后再加入。
  • 然后检查是否存在过期元素,若存在则删除。
  • 单调队列的队首元素永远是滑动窗口的最大值。
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
public class LeetCode_00239 {

// 单调队列
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0 || k <= 0) {
return new int[0];
}
int[] res = new int[nums.length - k + 1];
int index = 0;
Deque<Integer> q = new ArrayDeque<>(k);
for (int i = 0; i < nums.length; ++i) {
// 保持队列的单调性(严格从大到小)
while (!q.isEmpty() && nums[i] >= nums[q.peekLast()]) {
q.pollLast();
}
q.offerLast(i);
// 删除过期元素
if (i - q.peekFirst() >= k) {
q.pollFirst();
}
if (i >= k - 1) {
res[index++] = nums[q.peekFirst()];
}
}
return res;
}
}

方法三:利用窗口的传播性。时间复杂度为 O(n)。

  • 构建两个数组 left 和 right。
  • left 数组每个大小为 k 的窗口从左往右传播。
  • right 数组每个大小为 k 的窗口从右往左传播。
  • 则每个窗口的当前最大值为 max{right[i], left[i + k - 1]}
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
public class LeetCode_00239 {

public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0 || k <= 0) {
return new int[0];
}
int n = nums.length;
// left 从左向右传播
int[] left = new int[n];
// right 从右向左传播
int[] right = new int[n];
left[0] = nums[0];
right[n - 1] = nums[n - 1];
for (int i = 1; i < n; ++i) {
left[i] = (i % k == 0) ? nums[i] : Math.max(left[i - 1], nums[i]);
int j = n - 1 - i;
right[j] = (j % k == 0) ? nums[j] : Math.max(right[j + 1], nums[j]);
}
int[] res = new int[n - k + 1];
int index = 0;
for (int i = 0; i + k <= n; ++i) {
res[index++] = Math.max(right[i], left[i + k - 1]);
}
return res;
}
}