代码随想录算法训练营第13天 | 150. 逆波兰表达式求值, 239. 滑动窗口最大值,347.前 K 个高频元素
150. 逆波兰表达式求值
- 栈适用于相邻近的元素匹配消除的操作
- 前面做的两题一个是相邻近的括号匹配然后消除 一个是向邻近的字母一样消除 这题是一样的原理 邻近的2个数再遇到一个运算符就消除 再添加
- 注意点是 第一个数和第二个数的运算顺序 第二个Pop出来的在前面
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> zhan = new ArrayDeque<>();
for(String s: tokens){
if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/") ){
int num1 = zhan.pollLast();
int num2 = zhan.pollLast();
if(s.equals("+")){
zhan.addLast(num2+num1);
}else if(s.equals("-")){
zhan.addLast(num2-num1);
}else if(s.equals("*")){
zhan.addLast(num2*num1);
}else if(s.equals("/")){
zhan.addLast(num2/num1);
}
}else{
zhan.addLast(Integer.valueOf(s));
}
}
int res = zhan.pollLast();
return res;
}
}
239. 滑动窗口最大值
- 自定义维护一个单调队列 维护出口第一个元素最大 假如新进来的元素更大 就pop掉前面所有元素
- 假如新进来的元素比前面部分元素大 那么就Pop掉那部分元素
- 滑动的时候 判断队列出口元素是否等于需弹出元素 若不等于 则不用管 因为之前已经被pop掉了
- 准备新进来元素做判断时 可以用while循环 和当前队列尾部元素比大小
- 暴力解法为在每个滑动窗口中再遍历找最大 时间复杂度为 n*k
- 即用一个数组来接滑动窗口的元素 然后遍历找Max
- 用单调队列来找滑动窗口中的最大值 只用o(1) 因为最大值保持在队列出口
- 接下来在主函数中 就只需要遍历数组 每遍历一次就做一次pop push getMaxValue操作 用一个结果数组来接Max值
- 不过需注意 第一轮滑动窗口初始 先用一轮单独的循环装值进去 不用pop
单调队列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 1){
return nums;
}
int resLen = nums.length-k+1;
int[] res = new int[resLen];
Myqueue queue = new Myqueue();
int count = 0;
for(int i=0;i<k;i++){
queue.push(nums[i]);
}
res[count++] = queue.getMaxValue();
for(int i =k;i<nums.length;i++){
queue.pop(nums[i-k]);
queue.push(nums[i]);
res[count++] = queue.getMaxValue();
}
return res;
}
}
class Myqueue {
Deque<Integer> que = new LinkedList<>();
public void pop(int val){
if(!que.isEmpty() && que.peekFirst()==val){
que.pollFirst();
}
}
public void push(int val){
while(!que.isEmpty() && val>que.peekLast()){
que.pollLast();
}
que.addLast(val);
}
public int getMaxValue(){
return que.peekFirst();
}
}
347.前 K 个高频元素
- 小顶堆 大顶堆 都是通过二叉树实现
- 堆用于解决 topK 问题
- 小顶堆顶部为最小的元素 每次弹出最小元素
- 用map统计每个元素频率
- 用堆来解决前k个高频的问题 遍历Map<key,value>并存入堆
- 当堆size大于k时,pop
- 创建一个int[] 数组 倒序 来接堆pop
- java 用 PriorityQueue实现优先队列
小顶堆实现
class Solution {
public int[] topKFrequent2(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){//小顶堆只需要维持k个元素有序
if(pq.size()<k){//小顶堆元素个数小于k个时直接加
pq.add(new int[]{entry.getKey(),entry.getValue()});
}else{
if(entry.getValue()>pq.peek()[1]){//当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
pq.poll();//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
int[] ans = new int[k];
for(int i=k-1;i>=0;i--){//依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
ans[i] = pq.poll()[0];
}
return ans;
}
}
单调栈、单调队列及优先队列
-
单调队列 指队列中元素之间关系具有单调性,而且队首和队尾都可以出队,但是只有队尾可以进行入队操作。其重要作用是找到前n个或者后n个数的最值
-
具体操作是:假设单调队列是单调递减队列,假设在插入元素v时,将队列尾部的元素同v比较,如果队列尾部的元素不大于元素v,我们直接删除队尾元素,再将队尾元素与v比较,直至队尾元素比v大,这个时候我们将v插入队尾
-
优先队列 是一个队列,优先级高的会先出队列 (堆实现)
-
单调栈 也是保持栈内元素单调递增或单调递减。在插入元素时仍需保持栈内元素的单调性。如现有单调栈,其栈内元素为:1 4 5,这时我们将元素3插入单调栈的话;我们需要先将4 5弹出栈在将3入栈,操作之后栈内元素变为1 3。单调栈似乎也可以通过单调队列实现…
总结
通过括号匹配问题、字符串去重问题、逆波兰表达式问题来系统讲解了栈在系统中的应用,以及使用技巧。
通过求滑动窗口最大值,以及前K个高频元素介绍了两种队列:单调队列和优先级队列,