目录
Leetcode 239. 滑动窗口最大值
题目描述:给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
思路:本题算是单调队列的经典应用了,尤其是需要自己构造单调队列。根据题意,我们可以维护一个单调队列,这个单调队列的特点是:至多包含k个元素、队列内元素值递减。
为什么是至多呢?因为我们要控制队列内的元素值递减,这就导致当新的元素比队尾元素大时,需要将队尾元素出队,出队元素得个数可能为0~k,因此队列内元素个数可能比k小。
为什么要维护这样一个队列呢?本题的暴力做法是遍历数组的同时在窗口中找最大值,暴力做法本题会超时(很多题都是),因此我们需要减少遍历次数,由于本题只需要返回每个窗口的最大值,因此如果新入队的元素比队尾元素(也就是比所有元素)都小的话,就没必要管这个元素了。换句话说,只要该元素比队尾元素大,就可以让队尾元素出队,直到该元素比队尾元素小,该元素入队。
大体思路捋清楚之后,我们就可以利用STL的deque(双端队列)解决本题,代码如下:
class Solution {
private:
class MyQueue { //自定义递减的单调队列
public:
deque<int> que;
void pop(int value) { //比较队头元素和value是否相等,相等则弹出
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
void push(int value) { //只要value比队尾元素大,就将队尾元素弹出
while (!que.empty() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
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());
for (int i = k; i < nums.size(); i++) { //依次弹出和加入元素
que.pop(nums[i - k]);
que.push(nums[i]);
result.push_back(que.front());
}
return result;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(k)
Leetcode 347.前 K 个高频元素
题目描述:给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
思路:这道题目主要涉及到如下三块内容:
(1)要统计元素出现频率
(2)对频率排序
(3)找出前K个高频元素
其中(1)可以用哈希表储存元素值和该元素出现的频率,C++中可以使用STL中的map。而对频率排序可以使用优先队列。priority_queue(优先队列)的底层原理是利用max-heap(大顶堆)对元素排序,而这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
由于本题需要找出前k个高频元素,因此我们可以不用将所有频率排序,可以利用优先队列,且采用从小到大的排序方式(小顶堆),由于优先队列只能弹出第一个元素,只有用小顶堆的排序方式,才方便排除出现频率小的元素。举个例子:
思路捋清之后,代码实现需要注意的一点是:相比于写快排的cmp函数的时候,return left>right 就是从大到小,return left<right 就是从小到大。而优先队列正好相反,前者是小顶堆,后者是大顶堆
以下是代码实现:
class Solution {
public:
class myComparison{
public:
bool operator()(const pair<int,int>& l,const pair<int,int>& r){
return l.second>r.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
//统计元素出现频率
unordered_map<int,int> map;
for(int i=0;i<nums.size();i++){
map[nums[i]]++;
}
//对频率排序
//定义一个小根堆
priority_queue<pair<int,int>,vector<pair<int,int>>,myComparison> pri_que;
// 用固定大小为k的小顶堆,扫面所有频率的数值
for(auto it=map.begin();it!=map.end();it++){
pri_que.push(*it);
if(pri_que.size()>k){//如果堆大小超过了k,就弹出一个元素,也就是次数最少的元素
pri_que.pop();
}
}
//倒序输出k个高频元素
vector<int> result(k);
for(int i=k-1;i>=0;i--){
result[i]=pri_que.top().first;
pri_que.pop();
}
return result;
}
};
- 时间复杂度: O(nlogk)
- 空间复杂度: O(n)
总结:今天的题目思维难度有点大,虽然只有两道题,但是还是花了很多时间。
最后,如果文章有错误,请在评论区或私信指出,让我们共同进步!