题目
239. 滑动窗口最大值 - 力扣(LeetCode)
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | 示例 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]
 
 | 
提示:
| 12
 3
 
 | 1 <= nums.length <= 105-104 <= nums[i] <= 104
 1 <= k <= nums.length
 
 | 
思路一
说明
我最初自己想的思路是,用一个队列来维护滑动窗口,并用一个当前最大值来记录滑动窗口内的最大值。每次滑动窗口满了(为k),都将这个最大值写入返回值数组中。
当滑动窗口左侧缩限的时候,判断被出队列的是否为最大值
- 不是最大值,不用更新当前最大值;
- 是最大值,从队列剩余数据中选出一个新的当前最大值;
这样就能维护出一个题目需要的数组。代码如下
代码
| 12
 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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 
 | class Solution {public:
 vector<int> maxSlidingWindow(vector<int>& nums, int k) {
 int curMax = INT32_MIN;
 vector<int> retV;
 
 int size = 0;
 queue<int> que;
 for(auto&e:nums)
 {
 
 if(size == k)
 {
 int popNum = que.front();
 que.pop();
 size--;
 
 if(popNum == curMax)
 {
 int count = size;
 curMax = que.front();
 while(count --)
 {
 
 int temp = que.front();
 curMax = max(curMax,temp);
 que.pop();
 que.push(temp);
 }
 }
 
 }
 
 
 if(size < k)
 {
 curMax = max(curMax,e);
 que.push(e);
 size++;
 }
 
 
 if(size == k)
 {
 retV.push_back(curMax);
 }
 }
 
 if(nums.size() < k)
 {
 retV.push_back(curMax);
 }
 return retV;
 }
 };
 
 | 
这个代码的时间复杂度是O(N*K),因为最差情况视作每次都需要重新遍历队列,时间复杂度就很高了。但思路应该是没有问题的,通过了大部分测试用例,只不过在大测试用例中超时了,因为此时K很大,如果需要重新遍历对列找出第二个最大值,耗时会很高。

当然,我想出了另外一个优化方案,就是对于这种大k的情况,维护一个队列中第二大的数据,就不需要遍历对列了。但这样很麻烦,且这个思路本身就已经不适合解这道题了,于是没有尝试。
思路二
说明
思路二是代码随想录上面的,使用一个单调递增/递减对列来维护这个最大值。对于本题而言,使用单调递减序列更适合。
这个对列需要实现push/pop/front三个功能,其中front就是当前滑动窗口中的最大值:
- push:当当前值小于对列尾部值时,直接插入;大于时,出队列尾部数值,直到当前值小于队列尾部数值,插入;
- pop:如果滑动窗口删除的数据和队头数据一致,出队头数据(因为这是一个需要被删除的最大值);不一致则不做任何操作;
- front:获取队头数据;
注意,这个单调对列只是针对本体的需求来写的。本体只是需要滑动窗口中的最大值,对于对列而言并不需要维护滑动窗口中的所有元素,只需要维护几个递减的最大值就行了。
本题也不能用堆来实现,因为堆只能删除堆顶元素,堆顶元素不一定是滑动窗口需要移除的元素。这会导致堆内元素和当前滑动窗口内的元素不对应,会出现问题。
使用C++的deque数据结构就可以实现一个这样的对列。deque是stack/queue默认的底层容器,stack/queue在C++中是容器适配器(因为它们并不是直接实现的,而是借助其他容器实现的)。deque的特性让它支持队头队尾的插入删除操作。
| 12
 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
 
 | class MyQueue
 {
 deque<int> que;
 public:
 
 void pop(int value)
 {
 
 if (!que.empty() && value == que.front())
 {
 que.pop_front();
 }
 }
 
 
 void push(int value)
 {
 while (!que.empty() && value > que.back())
 {
 que.pop_back();
 }
 que.push_back(value);
 }
 
 
 int front()
 {
 return que.front();
 }
 };
 
 | 
代码
有了这个单调递减的对列后,我们现在只需要将vector中的元素按K滑动窗口大小往对列输入就可以了。注意,删除元素的时候要删除当前滑动窗口第一个元素,再加入新元素。
第一次遍历后需要插入一次最大值是因为第二个循环可能不会进去,而且第二个循环中会先移动窗口再插入新的最大值,如果不提前插入第一个最大值,可能会漏掉。
另外,本体限定K不会大于数组的长度,所以第一个循环用K做边界是不会数组越界的。
| 12
 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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 
 | class Solution {
 class MyQueue
 {
 deque<int> que;
 public:
 
 void pop(int value)
 {
 
 if (!que.empty() && value == que.front())
 {
 que.pop_front();
 }
 }
 
 
 void push(int value)
 {
 while (!que.empty() && value > que.back())
 {
 que.pop_back();
 }
 que.push_back(value);
 }
 
 
 int front()
 {
 return que.front();
 }
 };
 public:
 vector<int> maxSlidingWindow(vector<int>& nums, int k) {
 MyQueue que;
 vector<int> retV;
 
 for(int i =0;i<k;i++)
 {
 que.push(nums[i]);
 }
 
 retV.push_back(que.front());
 
 for(int i = k;i<nums.size();i++)
 {
 
 que.pop(nums[i - k]);
 que.push(nums[i]);
 
 retV.push_back(que.front());
 }
 return retV;
 }
 };
 
 | 

The end
单调递增/单调递减的对列思想还是第一次遇到,学到了。