代码随想录算法训练营第13天 | 239. 滑动窗口最大值 , 347.前 K 个高频元素

栈与队列的内部实现机制:https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html

239. 滑动窗口最大值

题目链接:https://leetcode.cn/problems/sliding-window-maximum/

思路:

这是一道使用单调队列的经典题目。
我们需要一个队列,放进窗口中的元素的同时,队列也一进一出,在移动的时候,队列会告诉我们里面的最大值是什么。
每次窗口移动的时候,调用pop、pus、front这几个方法,就可以得到我们想要的最大值。
而且,队列里的元素一定是要排序的,而且最大值要放在出队口。但是,以及排序后的队列,是无法把窗口中要移除的元素给弹出(这个元素不一定是最大值)。
其实,继续分析下去,队列不需要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素即可,同时要保障队列里的元素要是从大到小的。
这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。而实现单调单调队列绝不是只对窗口里的数字进行排序,那和优先级队列就是一样的了。
举个例子,以{2,3,5,1,4}为例,只需要维护{5,4}就够了,保证单调队列的单调递减。
在设计的时候,pop和push要遵从这样的规则:
(1)pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列直接弹出元素,否则的话不需要操作队列。
(2)push(value):如果push进来的元素的值,大于队列末尾的值,那么就将队首元素弹出(这里弹出的时候一定是要while循环,有可能多次弹出),直到push的元素小于等于队首的数值。把小于等于的值都加进来。
这样的话,只要访问que.peek()就可以知道当前谁是最大的值。

class MyQueue{
    Deque<Integer> deque = new LinkedList<>();
    // 弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
    // 同时判断队列当前是否为空
    void poll(int val){
        if(!deque.isEmpty() && val == deque.peek())
            deque.poll();
    }

    // 添加元素的时候,如果要添加的元素值大于入口处的元素,就将入口元素弹出
    // 保证队列元素单调递减
    // 比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
    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;
        }
        // nums [1,3,-1,-3,5,3,6,7]  k = 3
        int len = nums.length - k + 1;
        // 存放结果元素的数组
        int[] res = new int[len];
        int num = 0;
        // 自定义队列
        MyQueue myQueue = new MyQueue();
        // 先将前k的元素放入队列
        for(int i = 0; i < k; i++) {
            myQueue.add(nums[i]);
        }
        res[num++] = myQueue.peek();

        // 滑动窗口
        for(int i=k; i< nums.length; i++){
            // 滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
            myQueue.poll(nums[i-k]);
            myQueue.add(nums[i]);
            res[num++] = myQueue.peek();
        }       
        return res;
    }
}

时间复杂度:push和empty为O(1), pop和peek为O(n)
空间复杂度:O(n)

347.前 K 个高频元素

题目连接:https://leetcode.cn/problems/top-k-frequent-elements/description/

思路:

这道题目主要涉及到如下三块内容:
1 要统计元素出现频率
2 对频率排序
3 找出前K个高频元素
首先统计元素出现的频率,这一类的问题可以使用map来进行统计。
然后是对频率进行排序,这里我们可以使用一种 容器适配器就是优先级队列。他的内部是采用大顶堆(堆头是最大元素)或者小顶堆(堆头是最小元素)进行实现的。
本题我们就要使用优先级队列来对部分频率进行排序。他的时间复杂度会比快排更小,为nlogk,k为题目要求的k个元素。快排是整个数组进行排序,而我们这里只要维护k个有序的序列就可以。
这里是用大顶堆,还是小顶堆,需要想清楚。
如果是大顶堆的话,每次更新大顶堆,都会把最大的元素弹出去,无法保留下来前k个高频元素。
如果是小顶堆的话,因为要统计前k个最大的元素,只有小顶堆每次是将最小的元素弹出,最后剩下的就是前k个最大的元素。

注意

在建堆时,左大于右就会建立小顶堆,反之建立大顶堆。
但是我们在写快排的cmp函数的时候,return left>right 就是从大到小,return left<right 就是从小到大。
优先级队列的定义正好反过来了,可能和优先级队列的源码实现有关。需要注意一下。
在这里插入图片描述

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i:nums)
            map.put(i,map.getOrDefault(i,0)+1);
        // lambda表达式 设置优先级队列从小到大存储
        PriorityQueue<int[]> pq = new PriorityQueue<>((o1,o2) -> (o1[1]-o2[1]));

        for(Map.Entry<Integer,Integer> x:map.entrySet()){
            pq.add(new int[] {x.getKey(),x.getValue()});
            if(pq.size() > k){
                pq.poll();
            }
        }

        int[] res = new int[k];
        for(int i=0;i<k;i++){
            res[i] = pq.poll()[0];
        }
        return res;

    }
}

时间复杂度: O(nlogk) 比nlogn更快一些
空间复杂度: O(n)

栈与队列的总结

https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E6%80%BB%E7%BB%93.html

  • 27
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值