239. 滑动窗口最大值
文章链接:239. 滑动窗口最大值
思路
难点:
- 如何求窗口内的最大值
暴力法:遍历滑动窗口的最大值,时间复杂度O(n^k)
但是其实,滑动窗口的移动过程很想事一个队列,滑动窗口朝着下个元素移动的时候,很像pop掉了最左边的元素,push进最右边的元素,所以队列中始终维护窗口中的值。每次窗口滑动都调用一下getMaxvalue()函数,即当前的最大值是多少,直接告诉我。这样题目就很简单了,而且时间复杂度是O(n)
。但是很可惜,目前C++没有这样的数据结构。
同时优先级队列(大顶堆)也无法解决这个问题,因为大顶堆会自动给你排序,所以元素顺序改变了,我想pop的元素就不能正常pop了。
这里的解法是单调队列。即维护队列里面让其中的元素单调递增或者单调递减。
单调队列的模拟过程
详情见视频链接:单调队列正式登场!| LeetCode:239. 滑动窗口最大值
直接上伪代码!
主函数要干的事情就是pop()push()getMaxvalue()不停得维护那个最大值就行
deque<int> que;
pop(int val) //这个val告诉我们要pop什么元素,我们的滑动窗口每向后移动一下,我们就要把那个被踢出来的值传进来
{
if (!que.empty() && val == que.front())//判断和队列出口处的元素是否相等,如果等于出口处说明我们滑动窗口左边要遗弃的元素是窗口中的最大值
{
que.pop_front(); //此时我们的队列才真的去做pop,因为我们队列要维护的是窗口内的最大值
}
}
//push函数的代码非常重要,因为在传入窗口中的数字的时候,如果传了比出口处元素更大的数字,我们就拍把出口处的元素pop掉
push(int val)
{
while (!que.empty && val > que.back())
que.pop_back();
que.push_back(val);
}
//
getMaxvalue()
{
return que.front();
}
C++实现
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;
}
};
347.前 K 个高频元素
文章链接:347.前 K 个高频元素
思路
本题主要是有两个难点,
第一个如何求数组里面每个元素出现的频率;
第二个就是如何对频率进行排序并求前k个高频的元素。
其实计算元素出现的频率最好的就是使用map,其中key记录元素,value计算元素出现的次数。
然后按照value进行排序,最后输出前k个元素。
不过很重要的一点,这道题目我们其实不需要对所有元素都进行一个排序。我们求的是前k个高频元素,所以我们只维护k个高频元素的有序集合。
那么如何做呢? 大顶堆和小顶堆。堆的底层实现其实就是二叉树,大顶堆就是根结点的元素是最大的,即父亲总比左右两个孩子的数值大。小顶堆就是根结点元素是最小的,父亲总比左右两个孩子的数值小。
我们用堆遍历一遍map里面的所有元素,堆里边只维持k个元素。 这样时间复杂度缩小为O(nlogk)
。这里我们选用小顶堆。至于为啥可以看上文的文章或者视频链接。
伪代码如下:
for (i = 0; i < nums.size; i++)
{
map[nums[i]]++; // 收集元素出现的频率
}
priority_queue(<key, value>, compare); //定义小顶堆,同时对value进行从小到大的排序
for ( map ! it<key, value>)
que.push(it);
if(que.size > k) que.pop(); // 这里说明了只维护k个元素
//由于小顶堆先弹出的是频率较低的,但是我们的题目要求结果排序要从频率大的到频率小的
//定义一个数组,倒序遍历
vector<int> result
for(o = k - w1; i >0; i--)
result[i] = que.top().first();
que.pop()
代码
class Solution {
public:
// 小顶堆
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.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;
}
};