代码随想录算法训练营第十三天|LeetCode239.滑动窗口最大值、LeetCode347.前K个高频元素
2023年3月1日
第十五天补代码
2023年3月2日
第十六天补博客
239. 滑动窗口最大值
题目链接:239. 滑动窗口最大值
思路:
- 传统的思路是用一个队列来记录当前数组中所有的元素
- 然后每轮遍历队列找最大值,但这样的暴力解法会超时
- 这道题需要提出一个单调队列的思路
- 有两点思想需要理解:
- 如果新入队列的数大于当前队列的某些值,那么队列中原先的某些数就不再重要,可以直接抛出;如果插入的数大于队列中所有数,执行的结果会把原先所有的数都抛出
- 抛出窗口内元素的时候,很可能需要抛出的元素已经不在队列中了,只有一个情况还在队列中,那就是这个数是这个窗口的最大值
- 例如一个数组
1,[3,2,6],7,3,8
,当6
入队时,3
,2
就不再需要考虑,因为随着这个窗口移动,3
,2
都不会是最大值
- 然后每次需要找当前窗口的最大值的时候,只需要找当前队列的第一个元素就行
- 这个单调队列的入队和出队是这道题的难点,而如果把入队和出队封装起来的话,对于核心函数的逻辑就会变得非常简单
- 所以这道题主要就是要实现单调队列的入队和出队操作
- 代码随想录中把单调队列写成了要给类内的结构体,我在这里写了一个队列作为成员变量,再定义三个对这个队列操作的成员函数
时间复杂度:O(n)
,空间复杂度O(n)
代码:
class Solution
{
public:
vector<int> maxSlidingWindow(vector<int> &nums, int k)
{
vector<int> res;
// 先将前k个数放入单调队列,因为这边的代码逻辑不需要有出队操作
// 且只有放完k个元素之后才有求最大值的步骤
for (int i = 0; i < k; ++i)
{
push(nums[i]);
}
res.push_back(getmaxelem());
// 再遍历后面的元素,每次需出队,每次需找最大值
for (int i = k; i < nums.size(); ++i)
{
pop(nums[i - k]);
push(nums[i]);
res.push_back(getmaxelem());
}
return res;
}
// 入队
void push(int x)
{
// 队尾元素如果小于插入元素,就抛出
// 直到找到要给大于当前插入元素的位置,插入
while (monoque.size() > 0 && x > monoque.back())
monoque.pop_back();
monoque.push_back(x);
}
//
void pop(int x)
{
// 只有当要抛出的元素是当前窗口的最大值的时候
// 它才会仍然在单调队列中需要被抛出
if (x == monoque.front())
monoque.pop_front();
}
int getmaxelem()
{
return monoque.front();
}
std::deque<int> monoque;
};
总结:
- 思路很巧妙,相对也比较好理解,希望下次做的时候还会
- 做题体验:
嘤嘤
(哈哈
>嘿嘿
>哎哎
>嘤嘤
) - 时间:
未计
一刷
347. 前 K 个高频元素
题目链接:347. 前 K 个高频元素
这道题遇到了C++
中的适配器priority_queue
,接下来我讲分成两部分
- 题解
priority_queue
的简单实用
题解
思路:
- 存储每个数字出现过的个数,因该用kv键值对的形式,所以我们用一个
map
来存每个数字出现的次数 - 输出前k个出现次数最多的元素,可以根据
map
里的value
对元素进行排序,排序将耗费很多时间复杂度 - 因为只关心前k个大的值,这里引入一个我之前没有接触过的概念:优先级队列,将我们初始化好的map依次插入这个最大容量为k的队列中,如果value小于队列中的最小值,可以直接跳到下一个元素。插入的位置会由
C++
中的适配器priority_queue
来实现,底层逻辑是一个大根堆或小根堆 - 由于我们要求最大的k个数,所以用小根堆,因为小根堆的最小值在堆顶,方便我们插入的时候比较,并且在插入新值的时候抛出
时间复杂度:O(n)
,空间复杂度O(1)
代码:
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)
{
// 初始化map
std::map<int, int> my_map;
for (auto n : nums)
{
my_map[n]++;
}
// 定义priority_queue,三个参数的含义为:
// 1:存放的数据类型
// 2:存放的形式,一般为vecor或者deque
// 3:比较规则,大跟堆用less<数据类型>,小跟堆用greater<数据类型>,自定义数据类型需要自己定义
std::priority_queue<pair<int, int>, std::vector<pair<int, int>>, mycomparison>
p_q;
// 将键值对放入优先级队列中
for (auto kv : my_map)
{
p_q.push(kv);
if (p_q.size() > k)
{
p_q.pop();
}
}
// 队列中还剩的元素就是结果所需的元素,将其插入数组中
std::vector<int> res;
while(!p_q.empty())
{
res.push_back(p_q.top().first);
p_q.pop();
}
return res;
}
};
总结:
- 这里接触到了一种全新的容器(应该是适配器),
priority_queue
,stl还是需要好好学哦 - 做题体验:
嘤嘤
(哈哈
>嘿嘿
>哎哎
>嘤嘤
) - 时间:
未计
一刷
priority_queue
的简单使用
以下代码内容参考b站up主IT精品课程传送门
,视频连接在这里
priority_queue
默认是大顶堆
#include <iostream>
#include <iostream>
#include <queue>
using namespace std;
int main()
{
priority_queue<int> p_que;
p_que.push(8);
p_que.push(2);
p_que.push(124);
p_que.push(9);
p_que.push(4);
// 124 9 8 4 2
while (!p_que.empty())
{
/* code */
cout << p_que.top() << " ";
p_que.pop();
}
cout << "\n";
return 0;
}
默认是大顶堆,我们可以通过改变比较队则来调整大小顶堆
int main()
{
// 124 9 8 4 2
// priority_queue<int,vector<int>,less<int>> p_que;
// 2 4 8 9 124
priority_queue<int,vector<int>,greater<int>> p_que;
p_que.push(8);
p_que.push(2);
p_que.push(124);
p_que.push(9);
p_que.push(4);
while (!p_que.empty())
{
/* code */
cout << p_que.top() << " ";
p_que.pop();
}
cout << "\n";
return 0;
}
存放自定义数据类型,注意默认优先级队列是大根堆,要重载小于号
struct node
{
int x;
int y;
node() : x(0), y(0){};
node(int x, int y) : x(x), y(y){};
bool operator<(const node &b) const
{
return this->x < b.x;
}
};
int main()
{
priority_queue<node> que;
que.push((node){1, 5});
que.push((node){2, 6});
que.push((node){3, 2});
que.push((node){9, 1});
que.push((node){7, 7});
// 9 -> 1
// 7 -> 7
// 3 -> 2
// 2 -> 6
// 1 -> 5
while (!que.empty())
{
auto temp = que.top();
que.pop();
cout << temp.x << " -> " << temp.y << endl;
}
return 0;
}