力扣刷题之栈与队列(3) | 239. 滑动窗口最大值、347. 前 K 个高频元素

Leetcode 239. 滑动窗口最大值

题目链接

思路

根据滑动窗口的移动过程,每次移动时,很像是一个队列。每次移动完成三步操作:pop( ), push( ), getMaxValue( )
如果用优先级队列来解决这道题,由于我们需要获取最大值,那采用大顶堆(排序的二叉树,将最大值放在最上面)。每次将窗口中的数递减存入队列中,那么再次向后移动时,由于原来的数字顺序发生改变,我们可能无法直接pop掉原先的数值。所以不能使用优先级队列。
这里我们使用单调队列,即维护元素里面的单调递增和单调递减,和优先级队列不同,我们可以自定义元素的添加、pop、push。对于本道题,我们没有必要将滑动窗口内的所有数都加入队列,只需要维护可能成为最大值的序列

难点

设计单调队列的时候,pop,和push操作要保持如下规则:

  1. pop(value):如果窗口移除的元素==单调队列的出口元素,那么队列弹出元素,否则不用任何操作。
  2. push(value):如果窗口添加的元素>单调队列入口的元素,那么就将队列入口的元素弹出,直到push元素的数值<=队列入口元素的数值为止。

保持如上规则,每次窗口移动的时候,只要deque.front()就可以返回当前窗口的最大值

为了更直观的感受到单调队列的工作过程,以题目示例为例,输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3,动画如下:
在这里插入图片描述

代码(Java)

写法:自定义单调队列

class MyQueue{
    Deque<Integer> deque=new LinkedList<>();

    void poll(int val){
        //弹出元素时,需要比较需弹出的数值是否等于队列出口的数值
        if (!deque.isEmpty() && val == deque.peek())
            deque.poll();
    }

    void add(int val){
        //添加元素时,若添加的元素大于前面的元素,则弹出元素,直到添加元素的数值<=队列入口元素的数值为止
        while (!deque.isEmpty() && val > deque.getLast()){
            deque.removeLast();
        }
        deque.add(val);
    }

    int peek(){
        return deque.peek();
    }
}
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k){
        if (nums.length==1)
            return nums;

        int len = nums.length - k + 1;
        int[] res = new int[len];
        int index = 0;

        MyQueue myQueue = new MyQueue();
        //先将前k个元素放入队列
        for (int i = 0; i < k; i++) {
            myQueue.add(nums[i]);
        }
        res[index++]=myQueue.peek();

        //滑动窗口
        for (int i = k; i < nums.length; i++) {
            myQueue.poll(nums[i-k]);
            myQueue.add(nums[i]);
            res[index++]=myQueue.peek();
        }
        return res;
    }
}

代码随想录链接

下面是代码随想录的文章链接与视频链接,帮助大家更好地理解这道题。
文章
视频

Leetcode 347. 前 K 个高频元素

题目链接

思路

这道题需要做下列三个操作:

  1. 统计各元素出现的频率
  2. 对频率进行排序
  3. 找出前k个高频元素

由于需要统计各个元素出现的频率,根据之前所学的哈希,我们可以用HashMap来进行统计。key存放元素,value存放元素出现的次数

接着我们再按照value进行排序,按快速排序的时间复杂度,对所有元素进行排序为nlogn。但其实没有必要对所有元素的频率进行排序,我们只需要k个高频元素的有序集合,所以用到大顶堆(堆头是最大元素)和小顶堆(堆头是最小元素)。

难点

这道题我一开始认为采用大顶堆,因为需要求前k个高频元素,则有序集合里面的元素从大到小来排序,所以用大顶堆。若定义一个大小为k的大顶堆,当我们添加新的元素时,却pop掉了最大的元素

所以我们要用小顶堆,因为只有小顶堆pop掉最小的元素,最后小顶堆里积累的才是前k个最大元素。

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

代码(Java)

写法:小顶堆

时间复杂度:O(nlogn)
空间复杂度:O(n)

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i],map.getOrDefault(nums[i],0)+1);
        }

        //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
        PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
        for (Map.Entry<Integer,Integer> entry : map.entrySet()){
            if (priorityQueue.size()<k){
                priorityQueue.add(new int[]{entry.getKey(), entry.getValue()});
            }else {
                //当前元素出现次数大于小顶堆的根结点
                if (entry.getValue()>priorityQueue.peek()[1]){
                    priorityQueue.poll();
                    priorityQueue.add(new int[]{entry.getKey(),entry.getValue()});
                }
            }
        }
        int[] res = new int[k];
        for (int i = k-1; i >= 0 ; i--) {
            res[i] = priorityQueue.poll()[0];
        }
        return res;
    }
}

代码随想录链接

下面是代码随想录的文章链接与视频链接,帮助大家更好地理解这道题。
文章
视频

总结

239. 滑动窗口最大值中,由于队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的,这里使用了自定义单调队列
347. 前 K 个高频元素中,使用了优先级队列,这里我们也是对部分数据进行排序,而不是对所有数据排序。大家经常说的大顶堆(堆头是最大元素)小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接**用PriorityQueue(优先级队列)**就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fuego91

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值