day12休息
day13学习内容:150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素、栈的总结
150. 逆波兰表达式求值
后缀表达式(逆波兰表达式)
先把式子转为二叉树,下面的第一个式子为后缀表达式,第二个式子是常用的中序表达式,但缺点是需要加括号,所以计算机内部一般用后缀表达式(后续遍历 顺序是左右中)
解题思路
遇见数字就加入到栈里,遇到操作符就从栈里取出两个元素进行运算,结束后再放回栈里
代码
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long> sta;
for(int i=0; i<tokens.size(); i++){
long num, num1, num2;
if(tokens[i]=="+" || tokens[i]=="-" || tokens[i]=="*" || tokens[i]=="/"){
num1 = sta.top();
sta.pop();
num2 = sta.top();
sta.pop();
if(tokens[i]=="+") sta.push(num1+num2);
if(tokens[i]=="-") sta.push(num2-num1);
if(tokens[i]=="*") sta.push(num1*num2);
if(tokens[i]=="/") sta.push(num2/num1);
}
else sta.push(stoi(tokens[i]));
}
int result = sta.top();
return result;
}
};
int提交时会溢出,所以改为long
小结
栈非常擅长这种用相邻字符,并进行消除的操作,前面括号匹配也是这样
239. 滑动窗口最大值(单调队列)
题目分析
用队列解决,把队列当作窗口,每移动一次都去掉一个元素,添加一个元素,然后找最大
普通队列不好求最大值
如果使用优先级队列,放到大顶堆里(元素递减)或小顶堆,可能要删除的元素在中间,从而不好删除
所以要使用单调队列,关键在于如何push和pop元素,由自己定义
思路-自定义单调队列
设计单调队列的时候,pop,和push操作要保持如下规则:
- pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
- push(value):如果push的元素value大于入口元素的数值,那么就将队列出口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。
代码
先跳过本题,思路可以理解,感觉串起来写代码还有些难度
小节
单调队列不是一成不变的,不同场景有不同写法,只是要保证队列里单调递减或递增的原则
347.前 K 个高频元素(优先级队列)
题目分析
主要工作分三部分:1-先统计出频率;2-对频率进行排序;3-取出前k个高频对应的值
可以用map,用Key存值,value存其出现的次数,然后对value进行排序,即使是快排(二分法)时间复杂度也是n*logn
但没有必要都进行排序,可以用大顶堆(根节点比子节点大)和小顶堆(根节点比子节点小)数据结构来解决前k个高频或低频元素
如果用大顶堆,会在pop时把最大元素弹出去了,所以大顶堆是前k个低频元素,所以要用小顶堆,弹出的是堆顶的小元素
堆是二叉树,每次排序是维护k个元素,时间复杂度是n*logk
C++中有现成的优先级队列
思路
值和次数放到map中
定义优先级队列
用优先级队列把map遍历完
把优先级队列中元素倒着放入(频率高的在前面,频率低的在后面)
代码
对于大顶堆定义和遍历map不太熟悉,
顶堆可以等二叉树学完再回头看
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second; // 左大于右,小顶堆,右大于左,大顶堆
}
};
// 用map统计元素出现频率
unordered_map<int, int> map;
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){
pri_que.pop();
}
}
// 找出k个高频元素,因为小顶堆先弹出小的值,所以倒叙输出到数组
vector<int> result(k);
for(int i=k-1; i>=0; i--){
result[i] = pri_que.top().first; // 地址的话是->first,但存的是map(*it),则直接.first
pri_que.pop();
}
return result;
}
};
照着写了一遍,基本理解整体思路,主要对大顶堆定义不熟悉,但感觉是背
小结
优先级队列内部元素是自动依照元素的权值排列,在缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)
堆是一棵完全二叉树
栈的总结
队列的典型应用——单调队列、优先级队列两个比较难,第一遍不太熟悉,没有独立完成代码