题目

题目来源:力扣(LeetCode)

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。

示例 1:

输入: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
示例 2:

输入:nums = [1], k = 1
输出:[1]

示例 3:

输入:nums = [1,-1], k = 1
输出:[1,-1]

示例 4:

输入:nums = [9,11], k = 2
输出:[11]

示例 5:

输入:nums = [4,-2], k = 2
输出:[4]

思路分析

  1. 这道题可以使用单调队列来解决,我们维护一个单调递减的队列,将当前滑窗中的元素下标 依次加入到队列中。

  2. 当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。

  3. 由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。但与方法一中相同的是,此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要不断从队首弹出元素,直到队首元素在窗口中为止。

  4. 为了可以同时弹出队首和队尾的元素,我们需要使用双端队列。满足这种单调性的双端队列一般称作「单调队列」。

  1. /**
  2. * @param {number[]} nums
  3. * @param {number} k
  4. * @return {number[]}
  5. */
  6. var maxSlidingWindow = function(nums, k) {
  7. const size = nums.length;
  8. const queue = [];
  9. // 首先找出前 k 个元素中的最大值
  10. for (let i = 0; i < k; i++) {
  11. // 如果队列不为空且当前考察元素大于等于队尾元素,则将队尾元素移除。
  12. // 直到,队列为空或当前考察元素小于新的队尾元素,将当前考察元素添加到队列中
  13. while(queue.length && nums[i] >= nums[queue[queue.length - 1]]) {
  14. queue.pop();
  15. }
  16. // 将元素下标存储到队列中
  17. queue.push(i)
  18. }
  19. // 前 k 个元素中的最大值
  20. const res = [nums[queue[0]]]
  21. // 下标从 k 开始,查找以 k 为大小的窗口内的最大值
  22. for (let i = k; i < size; i++) {
  23. // 如果队列不为空且当前考察元素大于等于队尾元素,则将队尾元素移除。
  24. // 直到,队列为空或当前考察元素小于新的队尾元素,将当前考察元素添加到队列中
  25. while(queue.length && nums[i] >= nums[queue[queue.length -1]]) {
  26. queue.pop()
  27. }
  28. // 将元素下标存储到队列中
  29. queue.push(i);
  30. // 当队首元素的下标小于滑动窗口左侧边界left时
  31. // 表示队首元素已经不在滑动窗口内,因此将其从队首移除
  32. while (queue[0] <= i - k) {
  33. queue.shift()
  34. }
  35. // 队首元素是该窗口内的最大值
  36. res.push(nums[queue[0]])
  37. }
  38. return res
  39. };