代码随想录算法训练营第十一天|Leetcode 239 滑动窗口最大值,347 前k个高频元素
Leetcode 239.滑动窗口最大值
题目链接 239. 滑动窗口最大值
文章链接 https://programmercarl.com/0239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.html
视频链接 https://www.bilibili.com/video/BV1XS4y1p7qj
思路
一开始是用的暴力解法,遍历一遍窗口算出一个最大值,遍历n次,每个窗口的长度为k,所以时间复杂度是O(n*m),经过一番实践超时了。
开始学习题解的优秀解法:1.假设有一个数据结构,能使滑动窗口每次只向后移动一个元素,就会返回最大元素,有点类似先进先出,初步假定队列可以实现。2.单单是队列不太够,还要求最大元素,所以就要求这个队列的出口元素要是动态变化的,而且在push一个元素后,还要保持最大,单调队列可以实现,但是这个数据结构要我们自己实现,最主要的就是实现三个方法,pop(),push(),getMaxValue()。3.没必要去遍历每个窗口的元素,只需要维护每个滑动窗口可能的最大值即可。在实现时,就是push一个元素(滑动窗口右移一次),要比较之前的元素,只要比自己小就要把小的元素从队列中弹出,否则压入队列。
4.有一种情况,比如一个滑动窗口是(5,3,1),k=3,显然这三个数都应该在队列中,再往后遍历一个元素,这时队列的第一个元素就不得不弹出,对应的pop()方法是当前元素(nums[i-k]是队列的首元素时)。5.getMaxValue()就是返回队列的第一个元素即可,因为第一个元素是每个滑动窗口的最大值
代码
- 看完题解思路之后写的
class Solution {
public:
void pop(int val,deque<int> &que){
//队列不为空,且要弹出的值正好是队列出口的值(最大值,队列中有k个元素不得不弹出去)
if(!que.empty()&&val==que.front()){
que.pop_front();
}
}
void push(int val,deque<int> &que){
//直到队列为空,或者直到压入的值小于等于队列的尾部元素循环结束,目的是创造单调性
while(!que.empty()&&val>que.back()){
que.pop_back();
}
que.push_back(val);
}
int getValueMax(deque<int> &que){
//因为是单调递减的,所以队列入口值为每一次滑动窗口的最大值
return que.front();
}
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> que;
vector<int> result(nums.size()-k+1);
int m=0;
//先把前k个元素压入
for(int i=0;i<k;i++){
this->push(nums[i],que);
}
result[m++] = this->getValueMax(que);
for(int i=k;i<nums.size();i++){
this->pop(nums[i-k],que);
this->push(nums[i],que);
result[m++] = this->getValueMax(que);
}
return result;
}
};
- 题解的(把单调队列用类封装起来,可读性更高)
class Solution {
private:
class MyQueue { //单调队列(从大到小)
public:
deque<int> que; // 使用deque来实现单调队列
// 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
// 同时pop之前判断队列当前是否为空。
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
// 这样就保持了队列里的数值是单调从大到小的了。
void push(int value) {
while (!que.empty() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
// 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
int front() {
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> result;
for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
que.push(nums[i]);
}
result.push_back(que.front()); // result 记录前k的元素的最大值
for (int i = k; i < nums.size(); i++) {
que.pop(nums[i - k]); // 滑动窗口移除最前面元素
que.push(nums[i]); // 滑动窗口前加入最后面的元素
result.push_back(que.front()); // 记录对应的最大值
}
return result;
}
};
Leetcode 347.前k个高频元素
题目链接 347. 前 K 个高频元素
文章链接https://programmercarl.com/0347.%E5%89%8DK%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.html
视频链接 https://www.bilibili.com/video/BV1Xg41167Lz
思路
这道题和上面的题很像,先存储每个元素的出现次数,然后再排序返回前k个元素,即出现次数最多的k个元素。排序如果快排的话O(logn),但是没必要全部排序,只需要维护前k个最大的值即可。即每次入队列的元素要进行一次排序,根据题目,我们要pop()出最小的(剩下大的)——优先级队列。
代码
class Solution {
private:
//重载priority_queue的比较函数对象
class mycomparison {
public:
//operator()重载函数调用运算符
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
vector<int> result(k);
//存储每个数的出现次数
unordered_map<int,int> map;
int m=0;
//优先队列,数据类型是pair<int,int>,存储数和其出现次数
priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> que;
for(int i=0;i<nums.size();i++){
map[nums[i]]++;
}
//优先级队列(小顶堆)每次维护两个出现次数最大的数
for(auto it=map.begin();it!=map.end();it++){
que.push(*it);
//条件要大于k,每次pop()时队列中要有k+1个元素,pop()出一个后剩下k个,
if(que.size()>k){
que.pop();
}
}
for(int i=k-1;i>=0;i--){
result[i] = que.top().first;
que.pop();
}
return result;
}
};