代码随想录【day 12 栈与队列】| 239. 滑动窗口最大值 347.前 K 个高频元素
LeetCode 239. 滑动窗口最大值
题目链接:239.滑动窗口最大值
卡哥文解
视频讲解
- 题目描述: 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
- 返回 滑动窗口中的最大值 。
解题思路(双向队列)
1、自定义一个队列 放进窗口里的元素,随着窗口的移动,队列也一进一出,每次移动之后队列给出里面的最大值。
2、声明了一个变量 deque< int>que
,用于存储下标。
这个变量有以下 特点:
- 变量的最前端(即 que.front())是此次遍历的最大值的下标
- 当遇到新的数时,将新的数和双向队列的末尾(也就是que.back())比较,如果末尾比新数小,则把末尾扔掉,直到该队列的末尾比新数大或者队列为空的时候才停止,做法有点像使用栈进行括号匹配。
- 双项队列中的所有值都要在窗口范围内
3、模拟过程
初始状态:L=R=0,队列:{}
i=0,nums[0]=1。队列为空,直接加入。队列:{1}
i=1,nums[1]=3。队尾值为1,3>1,弹出队尾值,加入3。队列:{3}
i=2,nums[2]=-1。队尾值为3,-1<3,直接加入。队列:{3,-1}。此时窗口已经形成,L=0,R=2,result=[3]
i=3,nums[3]=-3。队尾值为-1,-3<-1,直接加入。队列:{3,-1,-3}。队首3对应的下标为1,L=1,R=3,有效。result=[3,3]
i=4,nums[4]=5。队尾值为-3,5>-3,依次弹出后加入。队列:{5}。此时L=2,R=4,有效。result=[3,3,5]
i=5,nums[5]=3。队尾值为5,3<5,直接加入。队列:{5,3}。此时L=3,R=5,有效。result=[3,3,5,5]
i=6,nums[6]=6。队尾值为3,6>3,依次弹出后加入。队列:{6}。此时L=4,R=6,有效。result=[3,3,5,5,6]
i=7,nums[7]=7。队尾值为6,7>6,弹出队尾值后加入。队列:{7}。此时L=5,R=7,有效。result=[3,3,5,5,6,7]
实现难点
1、pop操作判断:que不为空 并且 当前元素是否与队列出口元素的数值相等
2、push操作判断:que不为空 并且 当前元素比队列出口元素小
3、此时单调队列的前端元素就是最大值
补充
- 单调队列可以根据不同场景定义不同写法,保证队列里单调递减或递增的原则。
- deque是 stack和queue默认的底层实现容器,可以两边扩展,deque里的元素并不是严格连续分布。
代码实现
class Solution {
public:
// 定义单调队列
class MyQueue{ // 单调队列(从大到小)
public:
deque<int> que; // 使用deque来实现单调队列
// 弹出元素时,比较当前要弹出的数值是否等于队列出口元素的数值 相等则弹出
// pop之前判断队列当前是否为空
void pop(int value){
if(!que.empty() && value == que.front()){
que.pop_front();
}
}
// push的数值大于入口元素的数值 将队列后端的数值弹出 直到push的数值≤入口元素
// 这样保持队列里的数值是单调从大到小
// 在例子[1,3,-1,-3,5,3,6,7]中 一开始传入的value是1 比3(que.back())小 故不push 1
void push(int value){
while(!que.empty() && value > que.back()){
que.pop_back();
}
que.push_back(value);
}
// 查询当前队列里的最大值 直接返回队列前端front
int front(){
return que.front();
}
};
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;
}
};
LeetCode 347.前 K 个高频元素
题目链接:LeetCode347.前K个高频元素
卡哥文解
视频讲解
- 题目描述: 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
解题思路(大顶堆和小顶堆)
- 求数组里每个元素的频率 (
利用map 记录 key 以及对应的频率 value
) - 对频率进行排序 (若对所有元素排序 时间复杂度(快排):O(nlogn))
使用一种容器适配器 优先队列
- 找出前K个高频元素
只维护 k个元素的堆
若使用大顶堆 若有新的元素push进来时 则要将根节点最大的元素pop
故使用 小顶堆 每次新元素进来 将最小的元素pop出去
补充
- 优先队列的底层实现就是堆:对外接口从队头取元素,从队尾添加元素
- 优先队列内部元素自动按照元素的权值排列:缺省情况下 priority_queue利用max-heap(大顶堆)对元素排序 故自定义比较形式mycomprision
- 大顶堆是以vector为表现形式的完全二叉树
堆是一棵完全二叉树,树中每个节点的值都不小于(或不大于)其左右孩子的值
大顶堆:父亲结点 ≥ 左右孩子
小顶堆:父亲结点 ≤ 左右孩子
语法补充
1、C++ pair的基本用法总结
pair 将2个数据合成一组数据 STL中的map将key和value放在一起来保存
当一个函数需要返回2个数据时 选择pair
pair的实现是一个结构体 主要的两个成员变量是 first second
2、priority_queue<Type, Container, Functional>;
Type是要存放的数据类型
Container是实现底层堆的容器,必须是数组实现的容器,如vector、deque
Functional是比较方式/比较函数/优先级
priority_queue;
此时默认的容器是vector,默认的比较方式是大顶堆less
3、emplace_back()函数
是 C++ 11 新增加的,其功能和 push_back() 相同,都是在 vector 容器的尾部添加一个元素。
代码实现(哈希unordered_map+优先队列priority_queue)
class Solution {
public:
// 小顶堆
class mycomparision{
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; // 存储元素key以及对应的频率 value
for(int i=0;i<nums.size();i++){
map[nums[i]]++;
}
// 对频率排序
// 定义一个小顶堆 大小为k
// 创建优先队列
priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparision> pri_que;
// 固定大小为k的小顶堆 遍历map的元素
for(auto& a:map){
pri_que.push(a);
if(pri_que.size()>k){
pri_que.pop();
}
}
// 记录结果
vector<int>res(k);
// 倒序输出
for(int i=k-1;i>=0;i--){
res[i]=(pri_que.top().first); // first对应的是元素
pri_que.pop();
}
return res;
}
};
栈和队列总结
栈
1、 括号匹配问题
有三种不匹配的情况,
- 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
- 第二种情况,括号没有多余,但是 括号的类型没有匹配上。
- 第三种情况,字符串里右方向的括号多余了,所以不匹配。
2、字符串去重问题
把字符串顺序放在一个栈中,如果相同 栈就弹出
3、逆波兰表达式问题
每个子表达式得出的结果压入栈 遇到运算符 分别讨论具体情况
队列
1、滑动窗口最大值 单调队列
2、求前 K 个高频元素 优先队列
day12总结复盘
1、学习了单调队列和优先队列
2、对于小顶堆的实现,C++语法不熟
参考链接
https://leetcode.cn/problems/top-k-frequent-elements/solutions/1283998/c-xiao-bai-you-hao-you-xian-dui-lie-de-j-53ay/