题目链接:239. 滑动窗口最大值
题目:
给你一个整数数组 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]
提示:
- 1 <= nums.length <= 105
- -104 <= nums[i] <= 104
- 1 <= k <= nums.length
思路和算法:
这是使用单调队列的经典题目。题的难点在于怎么求一个区间里的最大值。当然,使用暴力法一定能解决,但是时间复杂度是O(n x k)。这时可以想到用大顶堆(优先级队列)来存放窗口里的k个数字,这样就可以知道最大的值是多少了,但是这其中存在一个问题:窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值,所以不能用大顶堆。
此时考虑到窗口是移动的,可以用一个队列来实现,因为队列是先进先出的。对于这个队列,会随着窗口的移动,调用que.pop(滑动窗口移除元素的数值),que.push(窗口添加元素的数值),然后que.front()返回我们需要的最大值。我们没有现成的这种数据结构,所以我们需要自己实现这个队列。
我们需要的队列结构:
class MyQueue {
void pop(int value) {}
void push(int value) {}
int front() {}
}
分析一下,队列里的元素一定是要排序的,而且最大值放在出队口。放入窗口元素进队列的时候,需要弹出元素,那么怎么才能弹出需要移除的元素呢,这个元素可不一定是最大值。
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里的最大值的元素就可以了,同时保证队里的元素数值是由大到小的。
那么这个维护元素单调(递减或递增)的队列就是单调队列。C++里没有直接支持单调队列,需要我们自己来实现。
单调队列可不简单的对窗口里的元素数值排序,如果只是排序,那和优先级队列没有什么区别。
下面是就本题示例来讲解实现思路:
对于窗口里的元素,只需要维护最大值以及可能成为最大值的元素就够了,保持单调队列里单调递减,此时队列出口元素就是窗口的最大元素。
设计单调对立时,pop和push操作要保持如下规则:
(1)pop(val):如果窗口移除的元素val等于单调队列的出口元素,则弹出元素,否则不进行任何操作;
(2)push(val):如果push的元素val大于入口元素的数值,那么就将队列的入口元素弹出,直到push的元素数值不大于队列入口元素的数值为止。
保持上面的规则,每次窗口移动的时候,que.front()就是我们需要的窗口最大值。
代码(c++):
class Solution {
//定义一个单调队列,队列里元素从大到小排列
//deque:出口 <---------- 入口
class MyQueue {
deque<int> que; //用deque来实现单调队列
public:
//在每次弹出之前,比较当前要弹出的数值是否等于队列出口元素的数值,相等则弹出
//同时pop之前判断队列是否为空
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
//如果要push的数值value大于单调队列入口元素的数值,则弹出入口元素
//直至push的数值小于队列入口元素的数值,这样就保证了队列里的数值是从大到小
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 myque;
vector<int> res;
for (int i = 0; i < k; ++i) { //先往队列里放入前k个元素
myque.push(nums[i]);
}
res.push_back(myque.front()); //res里存放窗口里的最大值
for (int i = k; i < nums.size(); ++i) {
myque.pop(nums[i - k]); //移除窗口最前面的元素
myque.push(nums[i]); //加入元素到窗口最后面
res.push_back(myque.front()); //记录窗口里的最大值
}
return res;
}
};
- 时间复杂度:
O
(
n
)
O(n)
O(n)
nums中的每个元素最多被push_back和pop_back各一次。 - 空间复杂度:
O
(
k
)
O(k)
O(k)
定义了一个辅助队列。
注意:单调队列不是一成不变的,不同场景不同写法,但有一点确定的是,单调队列一定是单调递增或者单调递减的原则。本题单调队列仅适用于本题。