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

文章介绍了如何使用单调队列解决滑动窗口最大值问题,以及用堆排序找到数组中前K个高频元素的方法。单调队列在添加和删除元素时能保持队列内的数值单调性,从而高效地维护窗口内的最大值。而堆排序则用于在O(nlogk)的时间复杂度内找出频率最高的K个元素。
摘要由CSDN通过智能技术生成

 239. 滑动窗口最大值

最主要的是构造【单调队列】,用于解决以下场景

给你一个数组 window,已知其最值为 A,如果给 window 中添加一个数 B,那么比较一下 A 和 B 就可以立即算出新的最值;

但如果要从 window 数组中减少一个数,就不能直接得到最值了,因为如果减少的这个数恰好是 A,就需要遍历 window 中的所有元素重新寻找新的最值

为什么不选择使用优先级队列?

如果选择使用优先级队列,创建大(小)顶堆。可以更快的获取最值,但由于此题中,窗口是移动的,而大顶堆每次只能弹出最大值,无法移除其他数值,会造成大顶堆维护的不是滑动窗口里面的数值。

注:

其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。

那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。

观察滑动窗口的过程可以理解,需要在头部和尾部进行数据操作,所以选择双链表数据结构

// 单调队列基本操作实现
class MonotonicQueue {
    LinkedList<Integer> dequeue = new LinkedList<>();
    public void push(int val){
        // 将小于 val 的元素全部弹出删除
        while(!dequeue.isEmpty() && dequeue.getLast() < val){
            dequeue.pollLast();
        }
        // 最后将 val 添加入尾部
        dequeue.addLast(val);
    }

    public void pop(int val){
        //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
        //同时判断队列当前是否为空
        if(!dequeue.isEmpty() && val == dequeue.getFirst()){
            dequeue.pollFirst();
        }
    }

    public int max(){
        // 队列的队顶元素始终为最大值
        return dequeue.getFirst();
    }
}

剩下的便只需要模拟滑动窗口进行操作获取窗口内最大值:

完整代码如下:

// 单调队列基本操作实现
class MonotonicQueue {
    LinkedList<Integer> dequeue = new LinkedList<>();
    public void push(int val){
        // 将小于 val 的元素全部弹出删除
        while(!dequeue.isEmpty() && dequeue.getLast() < val){
            dequeue.pollLast();
        }
        // 最后将 val 添加入尾部
        dequeue.addLast(val);
    }

    public void pop(int val){
        //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
        //同时判断队列当前是否为空
        if(!dequeue.isEmpty() && val == dequeue.getFirst()){
            dequeue.pollFirst();
        }
    }

    public int max(){
        // 队列的队顶元素始终为最大值
        return dequeue.getFirst();
    }
}

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        MonotonicQueue window = new MonotonicQueue();
        List<Integer> res = new ArrayList<>();

        for(int i = 0;i < nums.length;i++){
            // 首先填充滑动窗口前 k - 1 个元素,再向其中添加元素进行操作
            if(i < k - 1){
                window.push(nums[i]);
            }else{
                // 窗口开始进行滑动
                // 添加新元素
                window.push(nums[i]);                     
                // 获取窗口最大值
                res.add(window.max());
                // 移除旧元素         
                window.pop(nums[i - k + 1]);
            }
        }

        int[] arr = new int[res.size()];
        for(int i = 0;i < res.size(); i++){
            arr[i] = res.get(i);
        }
        return arr;
    }
}

 347.前 K 个高频元素

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

  1. 要统计元素出现频率
  2. 对频率排序
  3. 找出前K个高频元素

统计元素出现频率 -> map来进行统计

排序则决定时间复杂度

该题我们选择堆排序,只需要维护k个排序数据使时间复杂度为O(nlogk)

完整代码如下:

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

        PriorityQueue<Map.Entry<Integer,Integer>>
                pq = new PriorityQueue<>((entry1,entry2) -> {
            // 队列按照键值中对应的值从小到大排序
            return entry1.getValue().compareTo(entry2.getValue());
        });

        for(Map.Entry<Integer,Integer> entry : valToFreq.entrySet()) {
            pq.offer(entry);
            if(pq.size() > k){
                // 弹出最小元素,维护队列内是 k 个频率最大的元素
                pq.poll();
            }
        }

        int[] res = new int[k];
        for(int i = k - 1;i >= 0;i--){
            // res数组中存储前 k 个最大元素
            res[i] = pq.poll().getKey();
        }

        return res;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值