leetcode:239. 滑动窗口最大值

题目

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

示例:

  1. 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
  2. 输出:[3,3,5,5,6,7]
  3. 解释:
  4. 滑动窗口的位置 最大值
  5. --------------- -----
  6. [1 3 -1] -3 5 3 6 7 3
  7. 1 [3 -1 -3] 5 3 6 7 3
  8. 1 3 [-1 -3 5] 3 6 7 5
  9. 1 3 -1 [-3 5 3] 6 7 5
  10. 1 3 -1 -3 [5 3 6] 7 6
  11. 1 3 -1 -3 5 [3 6 7] 7
  1. 输入:nums = [1], k = 1
  2. 输出:[1]

本题同:
[困难] 剑指 Offer 59 - I. 滑动窗口的最大值

解答 & 代码

单调栈和单调队列:

  • 单调栈:用于解决 next greater number 问题,栈中存储的元素从栈底到栈顶严格单调递减
  • 单调队列:用于解决滑动窗口问题,寻找滑动窗口中的最大值,队列中存储的元素单调递减(严格的说是单调非递增,可以相等)

单调队列:用双端队列实现(因为既要在尾部删除、插入,也要在头部删除)

  1. // 单调队列(用双端队列实现)
  2. class MonotonicQueue {
  3. private:
  4. deque<int> q; // 双端队列
  5. public:
  6. // 在队尾插入元素 num,插入前先将前面比 num 小的元素都删除
  7. void push(int num)
  8. {
  9. while(!q.empty() && q.back() < num) // 注意是小于号
  10. q.pop_back();
  11. q.push_back(num);
  12. }
  13. // 删除队列头部的元素 num
  14. void pop(int num)
  15. {
  16. // 队列头部为 num 时才删除(不等说明前面的 push 操作时已被删除)
  17. if(!q.empty() && q.front() == num)
  18. q.pop_front();
  19. }
  20. // 返回最大值(即队列头部元素)
  21. int max()
  22. {
  23. return q.empty() ? -1 : q.front();
  24. }
  25. };
  26. // 解题框架
  27. class Solution {
  28. public:
  29. vector<int> maxSlidingWindow(vector<int>& nums, int k) {
  30. MonotonicQueue winQ; // 窗口,是单调队列,元素单调递减(严格的说是单调非递增,可以相等)
  31. vector<int> answer; // 结果数组
  32. for(int i = 0; i < nums.size(); ++i)
  33. {
  34. if(i < k - 1) // 先将前面的 k-1 个元素存入窗口(单调队列)
  35. winQ.push(nums[i]);
  36. else
  37. {
  38. winQ.push(nums[i]); // 窗口右边界右移一格,插入当前元素(新的右边界元素)
  39. answer.push_back(winQ.max()); // 将当前窗口的最大值存入结果数组
  40. winQ.pop(nums[i - k + 1]); // 窗口左边界右移一格,删除原左边界的元素
  41. }
  42. }
  43. return answer;
  44. }
  45. };

复杂度分析:设数组 nums 长为 n,窗口大小 k

  • 时间复杂度 O(n):nums 数组中每个元素最多 push 一次、pop 一次
  • 空间复杂度 O(k):窗口(单调队列)大小为 k

执行结果:

  1. 执行结果:通过
  2. 执行用时:224 ms, 在所有 C++ 提交中击败了 58.54% 的用户
  3. 内存消耗:128.8 MB, 在所有 C++ 提交中击败了 57.53% 的用户