239. 滑动窗口最大值
思路
- 暴力方法
遍历一遍的过程中每次从窗口中再找到最大的数值,这样很明显是O(n × k)的算法 - 单调栈
- 用大顶堆(优先级队列)来存放这个窗口里的k个数字,这样就可以知道最大的最大值是多少了,但是问题是这个窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值了。
- 用小顶堆存放,这样每次弹出的元素一定不是最大值
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来实现一个单调队列。不要以为实现的单调队列就是 对窗口里面的数进行排序,如果排序的话,那和优先级队列又有什么区别了呢。
代码实现
1.deque容器实现单调队列
常用的queue在没有指定容器的情况下,deque就是默认底层容器。单调栈在不同的情境下的书写规则是不一样的,最好是做到理解底层逻辑。
2. 实现函数
先把滑动窗口最大长度元素放进来,得到que.front( ),再开始一个一个历遍,只需要保存值得维护的有可能的最大元素,当value!=que.front( )时,就先保存front,因为它还没有历遍到需要弹出的位置。以此类推。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> que;
vector<int> result;
for (int i = 0; i < k; i++) {
while (!que.empty() && nums[i]> que.back()) {
que.pop_back();
}
que.push_back(nums[i]);//先找出前k个元素组成的单调队列
}
result.push_back(que.front());
for (int i = k; i < nums.size(); i++) {
if (!que.empty()&& nums[i - k]==que.front()) {
que.pop_front();
}//当保存的队列头部最大元素应该离开窗口了,就把它删除
while (!que.empty() && nums[i]> que.back()) {
que.pop_back();
}//继续维护单调队列
que.push_back(nums[i]);//取最大元素,即队头元素
result.push_back(que.front()); // 记录对应的最大值
}
return result;
}
};
时间复杂度: O(n)
空间复杂度: O(k)
总结
维护一个固定大小窗口的最大元素这个方法还是很实用的,需要更好的掌握。二刷之后发现自己落下的还是太多了。
347.前 K 个高频元素
思路
-
要统计元素出现频率
用map最合适,key记录元素类型,value记录元素出现次数 -
对频率排序
用优先队列就可以排好序。但优先队列分为大顶堆和小顶堆。
-第一反应题目要求前 K 个高频元素,那么果断用大顶堆。定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,无法保留下来前K个高频元素。
-所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。 -
找出前K个高频元素
寻找步骤如下:
代码实现
class Solution {
public:
// 小顶堆
class mycomparison{
public:
bool opretor()(const pair<int,int>& left,const pair<int,int>& right){
return left.second>right.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 要统计元素出现频率
unordered_map<int, int> map; // map<nums[i],对应出现的次数>
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 对频率排序
// 定义一个小顶堆,大小为k
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
// 用固定大小为k的小顶堆,扫面所有频率的数值
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k) { // 如果堆的大小大于了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)
总结
- 写优先队列的cmp函数的时候,return left>right 就是从大到小,return left<right 就是从小到大(刚好反过来),cmp函数写在类或者结构体里面
- priority_queue的定义中第一位为数据类型,第二位为储存容器,第三位为排序方法
- 当要用下标访问vector时需要定义容器大小 //vector result(k)
- pair的key值为first,value值为second
- map需应用unordered_map更方便
碎碎叨
今天题目有点难,好多不会的东西呜呜呜。加油加油加油!