第五章 栈与队列part02
150. 逆波兰表达式求值
本题不难,但第一次做的话,会很难想到,所以先看视频,了解思路再去做题
题目链接/文章讲解/视频讲解:https://programmercarl.com/0150.%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC.html
239. 滑动窗口最大值 (有点难度,可能代码写不出来,但一刷至少需要理解思路)
之前讲的都是栈的应用,这次该是队列的应用了。
本题算比较有难度的,需要自己去构造单调队列,建议先看视频来理解。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.html
347.前 K 个高频元素 (有点难度,可能代码写不出来,一刷至少需要理解思路)
大/小顶堆的应用, 在C++中就是优先级队列
本题是 大数据中取前k值 的经典思路,了解想法之后,不算难。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0347.%E5%89%8DK%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.html
总结
栈与队列做一个总结吧,加油
https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E6%80%BB%E7%BB%93.html
150. 逆波兰表达式求值
题目链接
https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/
解题思路
人的思维是中缀表达式 (1+2)(2+3)
对计算机而言中缀表达式是非常复杂的结构。
相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
12+23+
入栈遇到符号取出栈顶俩个元素,计算完加入栈,遇到符号在取出栈顶俩个元素依此类推
code
public int evalRPN(String[] tokens) {
Stack<Integer> stack=new Stack<>();
for(String token:tokens){
if("-".equals(token)||"+".equals(token)||"*".equals(token)||"/".equals(token)){
int bi=stack.pop();
int ai=stack.pop();
stack.push(evlaResult(ai,bi,token));
}else{
stack.push(Integer.valueOf(token));
}
}
return stack.pop();
}
private int evlaResult(int ai,int bi,String c){
if("-".equals(c)){
return ai-bi;
}else if("+".equals(c)){
return ai+bi;
}else if("*".equals(c)){
return ai*bi;
}else {
return ai/bi;
}
}
扩展将一个中缀表达式转化为逆波兰表达式
将一个普通的中缀表达式转换为逆波兰表达式的一般算法是:
首先需要分配2个栈,一个作为临时存储运算符的栈S1(含一个结束符号),一个作为存放结果(逆波兰式)的栈S2(空栈),S1栈可先放入优先级最低的运算符#,注意,中缀式应以此最低优先级的运算符结束。可指定其他字符,不一定非#不可。从中缀式的左端开始取字符,逐序进行如下步骤:
(1)若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈。
(2)若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,如果该运算符(不包括括号运算符)优先级高于S1栈栈顶运算符(包括左括号)优先级,则将该运算符进S1栈,否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符(包括左括号)低于(不包括等于)该运算符优先级时停止弹出运算符,最后将该运算符送入S1栈。
(3)若取出的字符是“(”,则直接送入S1栈顶。
(4)若取出的字符是“)”,则将距离S1栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入S2栈,此时抛弃“(”。
(5)重复上面的1~4步,直至处理完所有的输入字符。
(6)若取出的字符是“#”,则将S1栈内所有运算符(不包括“#”),逐个出栈,依次送入S2栈。
完成以上步骤,S2栈便为逆波兰式输出结果。不过S2应做一下逆序处理。便可以按照逆波兰式的计算方法计算了!
239. 滑动窗口最大值
题目链接
https://leetcode.cn/problems/sliding-window-maximum/description/
解题思路
定义一个单调递增队列,使用Deque实现,
每滑动一次窗口,移除队列头部元素,加入队尾元素,并收集最大值到res集合
code
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
MyQueue myQueue=new MyQueue();
List<Integer> res=new ArrayList<>();
//先将前k的元素放入队列 ,因为每次要移除最左元素,方便计算后续窗口
for(int i=0;i<k;i++){
myQueue.add(nums[i]);
}
res.add(myQueue.peek());
for(int i=k;i<nums.length;i++){
//滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
myQueue.poll(nums[i-k]);
//滑动窗口加入最后面的元素
myQueue.add(nums[i]);
res.add(myQueue.peek());
}
return res.stream().mapToInt(i->i).toArray();
}
static class MyQueue{
Deque<Integer> deque=new LinkedList<>();
//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
//保证队列元素单调递减
//比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
void add(int val){
while(!deque.isEmpty()&&val>deque.getLast()){
deque.removeLast();
}
deque.add(val);
}
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
void poll(int val){
if(!deque.isEmpty()&&val==deque.peek()){
deque.poll();
}
}
//返回队列中的最大值 ,最左
int peek(){
return deque.peek();
}
}
}
347.前 K 个高频元素
题目链接
https://leetcode.cn/problems/top-k-frequent-elements/description/
解题思路
map 统计元素 和频率
使用大根堆和小根堆都可以,不过大跟堆的话需要把全部元素入堆,调整的时间复杂度就是O(logn),但如果是建立小根堆的话,树高始终为K(只有当前元素大于堆顶才要入堆),调整的时间复杂度为O(logK)细节就是先放入小根堆k个元素,然后在进来的元素依次和堆顶比较,如果大于堆顶就弹出堆顶元素,放入较大的元素,依此类推
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map=new HashMap<>();
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
PriorityQueue<int[]> pq=new PriorityQueue<>((x,y)->x[1]-y[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
Integer key = entry.getKey();
Integer value = entry.getValue();
//如果小根堆容量是k了就比较 是否弹出堆顶小的元素
if(pq.size()<k){
pq.offer(new int[]{key,value});
}else if(pq.peek()[1]<value){
pq.poll();
pq.offer(new int[]{key,value});
}
}
//完成后队列就是前k个高频元素的小根堆, 堆顶是k个元素之中频率最小的一个
int[] res=new int[k];
for(int i=0;i<k;i++){
res[i]=pq.poll()[0];
}
return res;
}