力扣239. 滑动窗口最大值
题目来源(力扣):
https://leetcode.cn/problems/sliding-window-maximum/description/
题目描述:
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。
滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
基本思路:
滑动窗口的经典应用,实际上考察的是对单调队列的理解和应用。
具体而言,维护一个单调递减的队列,维护滑动窗口移动过程中的最大值。
对于滑动窗口,每次移动时,相当于做了2件事情:
1、删去最前面的数
2、添加一个新数在末尾
只涉及2个数的增添
对于单调队列,每次滑动窗口移动时,也只做了2件事,
1、判断滑动窗口删除的数是否与单调队列的队头相同,如果相同就需要删除队头元素。(相当于新窗口已经将之前的大数删去了)
2、判断滑动窗口新增加的数(一下简称“该数”)是否小于等于单调队列的队尾,如果成立则将该数直接添加到单调队列的队尾(使其成为单调队列中最新且最小的数);
否则不断弹出单调队列队尾的数,直到找到单调队列的末尾的数字大于该数(或者单调队列中所有数都被弹出),再将该数加入到末尾
即,每次滑动窗口移动后,对单调(递减)队列进行维护更新,更新完毕后,单调队列的队头就是当前滑动窗口中的最大数,将其加入到答案数组ans中
注意,单调队列不是简单地对滑动窗口中的数进行排序,那样就成了优先队列了。
实际上,单调队列的长度总是小于等于滑动窗口的窗口大小(当且仅当滑动窗口中的所有元素依次从大到小排列时,单调队列的长度才等于滑动窗口的大小)
代码如下,
代码实现:
class Solution
{
public:
vector<int> maxSlidingWindow(vector<int> &nums, int k)
{
deque<int> qu; // 单调队列
vector<int> ans;
//初始的滑动窗口 [0,k]
for (int i = 0; i < k; i++)
{
while (qu.size() && qu.back() < nums[i])
{
qu.pop_back();
}
qu.push_back(nums[i]);
}
ans.push_back(qu.front());
//滑动窗口不断向后移动
for (int i = k; i < nums.size(); i++)
{
if (qu.front() == nums[i - k]) //每次移动先判断队首是否相同(即单调队列队头是否被滑动窗口删去了)
qu.pop_front();
while (qu.size() && qu.back() < nums[i]) //利用新数更新单调队列
{
qu.pop_back();
}
qu.push_back(nums[i]); //将新数插入到单调队列的队尾
ans.push_back(qu.front());
}
return ans;
}
};
时间复杂度
O(n)
补充
区间最大值其实还能使用树状数组、线段树来求解,不够由于此题的特殊性,只需要维护一个单调队列就能实现
《代码随想录》中,作者将这里的单调队列进行了进一步封装,实际上思路完全一致,可以学习参考
代码如下
class Solution
{
private:
class MyQueue //封装好自己的单调队列
{
public:
deque<int> qu;
void pop(int val)
{
if (qu.size() && qu.front() == val)
qu.pop_front();
}
void push(int val)
{
while (qu.size() && qu.back() < val)
qu.pop_back();
qu.push_back(val);
}
int front()
{
return qu.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int> &nums, int k)
{
MyQueue qu; // 单调队列
vector<int> ans;
for (int i = 0; i < k; i++)
{
qu.push(nums[i]);
}
ans.push_back(qu.front());
for (int i = k; i < nums.size(); i++)
{
qu.pop(nums[i - k]);
qu.push(nums[i]);
ans.push_back(qu.front());
}
return ans;
}
};
浙公网安备 33010602011771号