开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
1、写在前面
大家好,这里是【LeetCode刷题日志】。今天的两道题分别是:
- 滑动窗口最大值
- 前 K 个高频元素
2、内容
2.1、题一
链接:239. 滑动窗口最大值 - 力扣(LeetCode)
(1) 描述
(2) 举例
(3) 解题
这道题如果用暴力方法,在遍历一遍的过程中每次在窗口内找到最大值,这样时间复杂度就是。下面是暴力算法的优化:
(一)优先队列
优先队列的解题思路是,利用大根堆来存放滑动窗口中的k个数字,然后取堆顶元素即可。具体如下:
- 初始化时,将
nums数组中的前k个元素放入优先队列中,存入优先队列的元素实际上是二元组(num, index),这样可以方便后续判断栈顶元素的位置与滑动窗口的位置关系; - 接着,每向右移动窗口时,我们就将一个新的元素放入优先队列中,此时堆顶元素就是最大值;
- 由于该最大值可能并不在滑动窗口内,因此我们加一层
while循环,判断如果当前的堆中的最大值的索引值小于或等于i-k,则说明该最大值在数组中的位置是在滑动窗口的左侧,也就是不在滑动窗口内,此时可以将这个最大值删去; - 当不断剔除掉不合适的最大值后,退出
while循环时,当前的最大值就是滑动窗口内的最大值,此时存入结果即可。
具体代码如下:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// 定义一个优先队列 q
priority_queue<pair<int, int>> q;
// 先将数组 nums 前 k 个元素存入队列 q 中
for (int i = 0; i < k; ++i) {
q.emplace(nums[i], i);
}
// 将第一个滑动窗口的最大值存入结果
vector<int> rs = {q.top().first};
// 遍历数组
for (int i = k; i < nums.size(); ++i) {
// 将新元素存入优先队列中
q.emplace(nums[i], i);
// 当堆顶元素(最大值)不在滑动窗口中时,则不断删除堆顶堆元素,直到最大值出现在滑动窗口里
while (q.top().second <= i - k) {
q.pop();
}
// 将堆顶元素存入结果中
rs.push_back(q.top().first);
}
// 最后返回结果
return rs;
}
};
(二)单调队列
在本题中,单调队列的维护逻辑是:
- 当队列不为空并且当前数组元素大于队尾元素时,移除队尾元素
- 直到队列为空或者队尾元素大于当前数组元素时,再入队数组元素
按照这个逻辑就可以维护好本题的单调递减队列。
有了单调队列后,由于单调队列中的元素是单调递减的,因此队首元素必定是当前窗口中的最大值,因此将队头元素添加到结果中即可。
但需要注意,如果队首元素的下标小于滑动窗口左侧边界(即q.front() ≤ i-k)时,表明当前队首元素并不在滑动窗口内,因此需要再添加一条while循环处理,移除这种情况下的队首元素,直到退出while循环后,当前队首元素才是在窗口内的,将该队首元素添加到结果中即可。
具体代码如下:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// 定义一个双端队列容器,适合于序列两端频繁的添加或删除元素的需求
deque<int> q;
// 先处理第一个窗口中的元素(注意,单调队列中的值为单调递减的)
for (int i = 0; i < k; ++i) {
// 维护单调递减的队列:
// 当队列不为空并且当前元素`nums[i]`大于等于队尾元素时,移除队尾元素
while (!q.empty() && nums[i] >= nums[q.back()]) {
q.pop_back();
}
// 只有当数组元素小于队尾元素时,才能让数组元素入队(注意,这里入队的是元素的下标值)
q.push_back(i);
}
// 定义一个向量容器,用于存放结果,result[0] = nums[q.front()]
vector<int> result = {nums[q.front()]};
// 接着遍历数组(移动窗口),处理后续元素
for (int i = k; i < nums.size(); ++i) {
// 一样的,当队列不为空并且当前元素`nums[i]`大于等于队尾元素时,移除队尾元素
while (!q.empty() && nums[i] >= nums[q.back()]) {
q.pop_back();
}
// 存入当前元素的索引值到队列中
q.push_back(i);
// 当队首元素的下标小于滑动窗口左侧边界`i-k`时,则表示队首元素不在滑动窗口内,因此移除队首元素
while (q.front() <= i - k) {
q.pop_front();
}
// 将队首元素存入结果中
result.push_back(nums[q.front()]);
}
// 最后返回结果
return result;
}
};
2.2、题二
链接:347. 前 K 个高频元素 - 力扣(LeetCode)
(1) 描述
(2) 举例
(3) 解题
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 使用unordered_map容器来记录数组中元素出现的次数,即map<nums[i], 频数>
unordered_map<int,int>map;
// map<nums[i], 频数>,统计元素出现次数
for(int& val : nums){
map[val]++;
}
// 自定义一个排序规则,从小到大来排序,便于后续生成小根堆
struct myComparison{
bool operator()(pair<int,int>&p1, pair<int,int>&p2){
return p1.second > p2.second;
}
};
// 根据自定义的排序规则,创建一个优先级队列 q
priority_queue<pair<int,int>, vector<pair<int,int>>, myComparison> q;
// 遍历map,将map中的元素全部入队到优先级队列 q 中
for(auto& i : map) {
q.push(i);
}
// 不断删除掉队列中的最小值,直到留下最后k个元素(这k个元素就是前k个高频元素)
while(q.size() > k) {
q.pop();
}
// 定义一个向量容器来保存结果
vector<int> result;
// 当优先级队列不为空时,将元素添加到结果中
while(!q.empty()){
result.emplace_back(q.top().first);
q.pop();
}
// 最后返回结果
return result;
}
};
3、写在最后
好的,今天就先刷到这里。