题意
给定一个数组 nums
和滑动窗口的大小 k
,请找出所有滑动窗口里的最大值。
解法1—暴力法
class Solution
{
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
if(nums.size()==0)
return {};
vector<int>res;
int max_num=nums[0]; //假设max_num是第一个滑动窗口的最大值
for(int i=1;i<k;i++)
max_num=max_num>nums[i]?max_num:nums[i];
res.emplace_back(max_num);
int index=k;
while(index<nums.size())
{
max_num=nums[index]; //假设max_num为当前滑动窗口的最大值
for(int i=1;i<k;i++) //寻找当前滑动窗口真正的最大值
{
max_num=max_num>nums[index-i]?max_num:nums[index-i];
}
res.emplace_back(max_num);
index++;
}
return res;
}
};
时间复杂度:O(n*k)
法2—优先队列
我们可以想到,对于两个相邻(只差了一个位置)的滑动窗口,它们共用着 k-1个元素,而只有 1 个元素是变化的。我们可以根据这个特点进行优化。
对于「最大值」,我们可以想到一种非常合适的数据结构,那就是优先队列(堆),其中的大根堆可以帮助我们实时维护一系列元素中的最大值。
初始时,我们将数组 nums 的前 k 个元素放入优先队列中,此时堆顶元素就是第一个滑动窗口的最大值。每当我们向右移动窗口时,我们就可以把一个新的元素放入优先队列中,此时堆顶的元素 就是上一个窗口和当前窗口元素中 所有元素的最大值。
但是这个最大值它在原数组中的位置可能并不在当前滑动窗口的范围内,即例如,第一个滑动窗口的最大元素是数组首元素,当滑动窗口往右走一步之后,将新的那个元素添加到优先队列中,但是新添加的元素没有第一个窗口的最大值大,此时优先队列堆顶元素还是第一个窗口的那个最大值,但是那个最大值所在的位置并不是第二个滑动窗口的范围了。
每当滑动窗口向右走的时候, 我们需要不断地移除堆顶的元素,直到其堆顶元素确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),表示元素num 在数组中的下标为index。
class Solution
{
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
if(0==k)
return nums;
priority_queue<pair<int,int>> pque; //优先级队列,默认是从大到小排序
vector<int> res; //存放所有滑动窗口内的最大值
//先将第一个滑动窗口内的元素放进队列中
for(int i=0;i<k;i++)
pque.push(pair<int,int>(nums[i],i));
res.emplace_back(pque.top().first); //将第一个窗口的最大值放入res中
for(int i=k;i<nums.size();i++)
{
pque.push(pair<int,int>(nums[i],i));
while( (pque.top().second)<=i-k )
{
//当前堆顶元素不在当前滑动窗口范围内
pque.pop();
}
res.emplace_back(pque.top().first);
}
return res;
}
};
法3—单调队列
我们需要一个队列,这个队列呢,放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是什么。队列里的元素一定是要排序的,而且要最大值放在出队口。如果把窗口里的元素都放进队列里,窗口移动的时候,队列需要弹出元素。
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队里里的元素数值是由大到小的。那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来一个单调队列。
设计单调队列的时候,pop,和push操作要保持如下规则:
- pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
- push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止(因为要保证队列内的元素是单调递减的)。
保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。
C++实现
class Solution
{
public:
//使用双端队列实现单调队列
class MyQueue
{
private:
deque<int>d;
public:
void push(int num)
{
//为了使插入元素后的队列依然保持单调属性
//我们需要保证双端队列的尾部元素要大于插入元素
while(!d.empty()&&num>d.back())
{
d.pop_back();
}
d.push_back(num);
}
void pop(int num)
{
//当需要弹出元素num的时候,我们只需要让num与队头元素相比即可
//如果num不等于队头元素,什么也不做,这并不会影响结果
if(!d.empty()&&num==d.front())
d.pop_front();
}
int front()
{
if(!d.empty())
{
return d.front();
}
else
return -1;
}
};
vector<int> maxSlidingWindow(vector<int>& nums, int k)
{
if(0==k)
return nums;
/*
priority_queue<pair<int,int>> pque; //优先级队列,默认是从大到小排序
vector<int> res; //存放所有滑动窗口内的最大值
//先将第一个滑动窗口内的元素放进队列中
for(int i=0;i<k;i++)
pque.push(pair<int,int>(nums[i],i));
res.emplace_back(pque.top().first); //将第一个窗口的最大值放入res中
for(int i=k;i<nums.size();i++)
{
pque.push(pair<int,int>(nums[i],i));
while( (pque.top().second)<=i-k )
{
//当前堆顶元素不在当前滑动窗口范围内
pque.pop();
}
res.emplace_back(pque.top().first);
}
return res;
*/
MyQueue que;
vector<int>res;
//先将第一个滑动窗口的元素添加到单调队列里面
//但是单调队列里有可能并不会维护窗口内所有的元素,只需维护有可能是最大元素的元素
for(int i=0;i<k;i++)
{
que.push(nums[i]);
}
res.emplace_back(que.front());
for(int i=k;i<nums.size();i++)
{
// 第 i-k 个元素已经不属于当前滑动窗口了,如果 第 i-k 个元素位于单调队列头部,
// 需要删除掉,否则无需其他动作。
que.pop(nums[i-k]);
que.push(nums[i]);
res.emplace_back(que.front());
}
return res;
}
};