代码随想录 | Day14 | 栈和队列:滑动窗口最大值&&前K个高频元素
主要学习内容:
1.使用单调队列解决滑动窗口
2.使用优先级队列
3.map的第二个元素的排序问题
239.滑动窗口最大值
思路:
使用单调队列维护一个单调递减的队列,具体看这个例子
举例子来说明:
nums={2,1,4,2,3,2},k=3
假设你在坐飞机,你的面前是要飞越的山,数组就是它们的高度,你的视野范围即你能看到的山的数量是滑动窗口大小,题目要求是,你想要找到视野范围内最高的山并且记录下来
单调队列的模拟过程
-
首先是 2 1进入你的眼帘,我们把2 1入队
-
当4进入你的视野后,2和1就再也不可能是你视野中最高的山了
所以我们在 2 1 4中删去 2 1留下4,现在队中只剩下4,所以你在第一块视野内最高的山峰是4,记录下来
- 接着飞机飞到了2
2有可能成为你视野中的最大值,因为4可能会离开你的视野,即滑动窗口内已经不包含4了,所以入队
此时4仍然是视野内最高的山峰,记录下来
- 接着飞到了 3
再遇到3时就和2 1 遇到4一样,这里的2也不可能成为你视野中的最高的山峰了
所以删除2 此时队列剩下的是 4 3,4仍然是你视野中最高的山峰,记录下来
- 接着飞到了 2
2比3小,有可能成为你视野中最高的山峰,入队
注意,当飞机飞到了2,你的视野中就不包含4了,因为你的视野只能有3座山 此时是2 3 2,第一个2已经在第4步被删掉了
4不在视野内当然要删掉了,所以队列内只剩下3 2
最高的山峰就是3,记录下来,遍历到了数组末尾,遍历结束
模拟结束我们可以回答这样的问题
为什么要用单调队列?
因为要让队首元素成为我们要的最大值,它前面的比它小的在我们的队列中没有必要维护,因为它们不可能成为视野内最高的山峰就像4前面的 2 1一样
就像是4 2 3一样,没有遇到3时,2还有可能是最大值,遇到3之后2再也不可能成为最大值,所以要删去2留下 4 3保持队列单调递减,这样就只留下了最大值和接下来有可能成为最大值的山峰
代码实现
接下来的任务就是代码实现我们的模拟过程,最好用单调队列记录对应元素的下标而不是这个元素,具体原因可以看下面
分为4步
1.删除元素,让队列保持单调递减
//如果队列不为空并且当前的山峰高于队列末尾的山峰 就一直弹出,直到队列末尾元素大于等于当前山峰,保持了队列单调递减
//2 1 4 (此时4还没有进入队列)
//4>1 弹出1 4大于2 弹出2
//4 2 3
//3>2 弹出2 3<4 结束删除元素的过程
while(!q.empty()&&nums[i]>nums[q.back()])
q.pop_back();
2.元素入队
q.push_back(i);
3.检查队首元素是否已经滑出窗口
//当前元素和队首元素下标之间距离大于滑动窗口大小
if(i-q[0]+1>k)
q.pop_front();
4.记录答案
if(i>=k-1)
res.push_back(nums[q.front()]);
完整代码:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> q;
vector<int> res;
for(int i=0;i<nums.size();i++)
{
while(!q.empty()&&nums[i]>nums[q.back()])
q.pop_back();
q.push_back(i);
if(i-q[0]+1>k)
q.pop_front();
if(i>=k-1)
res.push_back(nums[q.front()]);
}
return res;
}
};
单调队列记录对应元素的下标而不是这个元素原因
下面是我自己写的代码,很明显不对
我是用队列存储元素,对应上面的四个步骤
1.删除操作
if里面删除的是 2 1 4这种情况
else 里面删除的是 4 2 3的2这种情况
2.入队
删完了直接入队
3.检查队列大小
我是直接用队列大小来检查队首是否滑出窗口,这显然不对
比如 4 2 3这种情况
已经把2删了只剩下 4 3当然队列大小当然比3小了,但是4已经不是窗口内了,所以导致了错误
4.记录答案
所以问题最大的就是检查队首有没有滑出队列
但是这个时候就发现,除了用队列大小也没其他方法,因为我们不知道队列首部那个元素的下标,也没法计算当前元素到队首的元素的距离,所以最好用队列存储元素的下标而不是相应的元素
其次的话两个删除操作可以二合一,这个可以改进
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> q;
vector<int> res;
for(int i=0;i<nums.size();i++)
{
if(!q.empty()&&nums[i]>q.front())
{
while(!q.empty())
q.pop_front();
q.push_back(nums[i]);
}
else
{
while(!q.empty()&&nums[i]>q.back())
q.pop_back();
q.push_back(nums[i]);
}
while(q.size()>k)
q.pop_front();
if(i>=k-1)
res.push_back(q.front());
}
return res;
}
};
单调队列 滑动窗口最大值【基础算法精讲 27】_哔哩哔哩_bilibili
参考的灵神的视频,大家可以看看
347.前K个高频元素
解法1:哈希+排序
很明显用map,key就是nums[i],val就是出现次数
然后根据val排序,输出前k个元素即可
需要注意的是自己写排序函数和把map转换成vector才能排序,没什么难点
class Solution {
public:
bool static cmp(pair<int,int> a,pair<int,int> b)
{
return a.second>b.second;
}
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> p;
vector<int> res;
for(auto c:nums)
p[c]++;
vector<pair<int,int>> v(p.begin(),p.end());
sort(v.begin(),v.end(),cmp);
for(int i=0;i<k;i++)
res.push_back(v[i].first);
return res;
}
};
解法2:优先级队列
首先要解决使用大顶堆还是小顶堆的问题
如果使用大顶堆,大的排在前面,在我们要加入新的元素的时候要进行弹出元素,那么大的元素就被弹出了,所以到最后我们求出来的其实是前K小的元素,而不是前K大的元素,所以要用小顶堆
思路:
先用map统计出现的频率,然后将map的元素pair对加入小顶堆,如果小顶堆的元素数量超过了k那就弹出一个元素,弹出去的一定是元素出现频率更小的元素,到最后小顶堆中只剩下前K个高频元素,把高频元素输出即可
class Solution {
public:
class cmp {
public:
bool operator()(const pair<int, int>& a, const pair<int, int>& b) {
return a.second > b.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> p;
vector<int> res;
for(auto c:nums)
p[c]++;
priority_queue<pair<int,int>,vector<pair<int,int>>,cmp> q;
for(auto c:p)
{
q.push(c);
if(q.size()>k)
q.pop();
}
while(k--)
{
res.push_back(q.top().first);
q.pop();
}
return res;
}
};
个人觉得不如就用哈希加map排序…
这个是对前K个进行排序,哈希是全部的进行排序