刷题day11 栈与队列下【逆波兰表达式求值】【滑动窗口最大值】【前 K 个高频元素】

⚡刷题计划day11 栈与队列继续,可以点个免费的赞哦~

往期可看专栏,关注不迷路,

您的支持是我的最大动力🌹~

目录

⚡刷题计划day11 栈与队列继续,可以点个免费的赞哦~

往期可看专栏,关注不迷路,

您的支持是我的最大动力🌹~

题目一:150. 逆波兰表达式求值

题目二:239. 滑动窗口最大值

题目三:347. 前 K 个高频元素


题目一:150. 逆波兰表达式求值

这不仅仅是一道经典好题,也展现出计算机的思考方式

  1. 逆波兰表达式求值

(https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/)

逆波兰表达式:是一种后缀表达式,所谓后缀就是指运算符写在后面。

平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。

该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。

  • 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中


主要思路:

了解逆波兰表达式后,思路便很清晰了:

遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中

需要注意:

注意辨析哪个数在运算符左边右边,因为用的栈的数据结构,原左边的数先进栈,运算符右边的数后进栈;

那么出栈pop时先出的就是后进的数,及运算符右边的数

AC代码如下:

class Solution {
    public int evalRPN(String[] tokens) {
​
        Deque<Integer> deque = new LinkedList();
​
        for(String s : tokens){
            if("+".equals(s)){
                deque.push(deque.pop()+deque.pop());
            }else if("-".equals(s)){
                deque.push(-deque.pop()+deque.pop());
            }else if("*".equals(s)){
                deque.push(deque.pop()*deque.pop());
            }else if("/".equals(s)){
                int temp1 = deque.pop();
                int temp2 = deque.pop();
                deque.push(temp2/temp1);
            }else {  
                deque.push(Integer.valueOf(s));
            }
        }
        return deque.pop();
​
    }
}

题目二:239. 滑动窗口最大值

  1. 滑动窗口最大值

(https://leetcode.cn/problems/sliding-window-maximum/description/)

滑动窗口最大值问题是一个典型的双端队列应用场景。

主要思路:

下面以箱子拿苹果举例,更加通俗易懂:

  1. 箱子(双端队列):你用一个特殊的箱子来装苹果,这个箱子两头都可以拿苹果,我们称之为“双端队列”。

  2. 保持苹果大小顺序:你决定只把大苹果放在箱子的前面,小苹果放在后面。这样,每次你从箱子前面拿苹果,都能保证拿到的是最大的苹果。

  3. 处理新苹果:每当你拿到一个新苹果时,你会做以下事情:

    • 移除小苹果:你检查箱子后面的苹果,如果新苹果比箱子后面的苹果大,你就会把箱子后面的小苹果拿出来扔掉,因为这些小苹果不可能是接下来你拿苹果时的最大苹果了。

    • 放入新苹果:然后,你把新苹果放到箱子的后面。

  4. 移动箱子:随着你不断拿苹果,你也会移动箱子,让箱子前面的苹果(已经被你拿过的)逐渐移出箱子。

  5. 记录最大苹果:每次你移动箱子后,箱子前面的苹果就是当前箱子里最大的苹果,你把它记录下来。

  6. 重复:你重复这个过程,直到所有的苹果都被你检查过。

AC代码及注释如下:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> deque = new ArrayDeque<>(); // 创建一个双端队列
        int n = nums.length; 
        int[] res = new int[n-k+1]; // 存储结果的数组,长度为 n - k + 1
​
        for (int i = 0; i < n; i++) {
            // 维护队列的单调性
            while (!deque.isEmpty() && nums[deque.getLast()] <= nums[i]) {
                deque.removeLast(); // 如果队列的最后一个元素的值小于等于当前元素,则移除
            }
            deque.addLast(i); // 将当前索引 i 加入队列
​
            // 维护队列的窗口大小
            if (i - deque.getFirst() >= k) { // 如果队列的第一个元素已经不在窗口内,则移除
                deque.removeFirst();
            }
​
            // 记录答案
            if (i >= k - 1) {
                // 当窗口已经形成,记录队列第一个元素的值作为当前窗口的最大值
                res[i - k + 1] = nums[deque.getFirst()];
            }
        }
        return res; // 返回结果数组
    }
}

题目三:347. 前 K 个高频元素

  1. 前 K 个高频元素

(https://leetcode.cn/problems/top-k-frequent-elements/description/)

主要思路

这道题目主要涉及到如下三块内容:

  1. 要统计元素出现频率

  2. 对频率排序

  3. 找出前K个高频元素

首先统计元素出现的频率,这一类的问题可以使用map来进行统计,key-value。

然后是对频率进行排序,这里我们可以使用一种 容器适配器就是优先级队列

什么是优先级队列呢?

其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。

而且优先级队列内部元素是自动依照元素的权值排列。那么它是如何有序排列的呢?

缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。

什么是堆呢?

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。

本题我们就要使用优先级队列来对部分频率进行排序。

为什么不用快排呢, 使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。

此时要思考一下,是使用小顶堆呢,还是大顶堆?

有的同学一想,题目要求前 K 个高频元素,那么果断用大顶堆啊。

那么问题来了,定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。

而且使用大顶堆就要把所有元素都进行排序,那能不能只排序k个元素呢?

所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

寻找前k个最大元素流程如图所示:(图中的频率只有三个,所以正好构成一个大小为3的小顶堆,如果频率更多一些,则用这个小顶堆进行扫描)

AC代码及注释如下:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>(); //key为数组元素值,val为对应出现次数
        for(int num : nums){
            map.put(num,map.getOrDefault(num,0)+1);
        }
        // 优先队列默认大根堆,我们需要小根堆
        // lambda 表达式设置优先级队列从大到小存储 o1 - o2 为从小到大,o2 - o1 反之
        //在优先队列中存储二元组(num, cnt),cnt表示元素值num在数组中的出现次数
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)-> pair1[1]-pair2[1]);
​
        for(Map.Entry<Integer,Integer> entry : map.entrySet()){
            if(pq.size()<k){
                pq.add(new int[]{entry.getKey(),entry.getValue()});
            }else {
            //pq.peek()[1]这个表达式获取peek()方法返回的数组的第二个元素,即队列头部元素的出现次数。
                if(entry.getValue()>pq.peek()[1]){//
                    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;
​
    }
}

栈和队列专题就结束了,下期我们开始二叉树专题,加油加油!
赞赞+关注~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值