代码随想录 day 11 栈与队列

第五章 栈与队列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;
    }
  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值