滑动窗口最大值
239. 滑动窗口最大值 - 力扣(LeetCode)
题目大意,返回每个窗口内的最大值。
思路-优先队列
优先队列(堆),其中的大根堆可以实时维护一系列元素中的最大值。
每当我们向右移动窗口时,我们就可以把一个新的元素放入优先队列中,此时堆顶的元素就是堆中所有元素的最大值。然而这个最大值可能并不在滑动窗口中,我们不断地移除堆顶的元素,直到其确实出现在滑动窗口中。
时间复杂度:O(nlogn)
。在最坏情况下,数组 nums 中的元素单调递增,那么最终优先队列中包含了所有元素,没有元素被移除。维护优先队列的时间复杂度为 O(logn)
,因此总时间复杂度为 O(nlogn)
。
空间复杂度:O(n)
,即为优先队列需要使用的空间。
代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// 优先队列,保存值和下标,按值维护大根堆
priority_queue<pair<int, int>> q;
vector<int> res;
int n = nums.size();
for(int i = 0; i < n; i ++) {
// 加入堆
q.push({nums[i], i});
// 如果这个最大值已经是之前窗口的,就丢弃
while (q.top().second <= i - k) {
q.pop();
}
// 每次保存当前最大的数
if(i >= k - 1)
res.push_back(q.top().first);
}
return res;
}
};
思路-单调队列
-
一个窗口内最需要关注的就只有最大值。比如窗口
[1 3 -1]
中,我们只关注最大值3
。换句话说,对于1
和-1
来说,因为窗口内存在比他大的数,那他们本身其实在本窗口没什么作用。既然其他的没什么作用,那只需要保存最大值其他值就不管咯?
也不是,因为这些值可能在后面的窗口有作用。比如,上述
[1 3 -1]
的下两个窗口假如是[-1 -3 -5]
,那-1
就是最大值了。 -
考虑后续多个窗口,则需要降序维护多个“最大值”。如上所述,结合
窗口内的最大值才被关注
和有比我大的值我就需要隐忍(避其锋芒,暂居幕后)
可以分析出这题适合使用单调队列。
单调队列中维护现在能用到或将来能用到的“最大值们”,最多k
个,空间复杂度为O(k)
。
遍历数组每个元素一次,维护单调队列(每个元素最多入队出队一次),时间复杂度为O(n + n)
,即O(n)
。
代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// 单调双端队列, 按实际值递减存储
// 保存的是下标
deque<int> d;
vector<int> res;
int n = nums.size();
for(int i = 0; i < n; i ++) {
// 判断队列维护的跨度是否超过k了。超过了代表是之前窗口的元素,已经用不到了
if(d.size() && i - d.front() >= k)
d.pop_front();
// 把比自己小的数都踢出去,因为只要自己在,就轮不到比他小别的当大哥
// 一样大的踢不踢都行
while(d.size() && nums[d.back()] < nums[i]) {
d.pop_back();
}
d.push_back(i);
// 每次保存当前最大的数
if(i >= k - 1)
res.push_back(nums[d.front()]);
}
return res;
}
};