【优选算法】专题十四——优先级队列

文章目录

一、数据流中的第 K 大元素

Leetcode链接

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

KthLargest(int k, int\[\] nums) 使用整数 k 和整数流 nums 初始化对象。

int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

解题思路

  • 使用PriorityQueue解决TopK问题

代码实现及解析

java 复制代码
class KthLargest {
    public int _k;
    public PriorityQueue<Integer> heap;

    public KthLargest(int k, int[] nums) {
        this._k=k;
        this.heap=new PriorityQueue<>();//建小根堆(Integer实现了Comparable接口,不用再传比较器了)
        //将元素放到堆中
        for(int x:nums){
            heap.offer(x);
            if(heap.size()>_k){//如果heap大小超过了k,就将堆顶元素弹出,最后堆中剩下的就是前k大元素
                heap.poll();
            }
        }
    }
    
    public int add(int val) {
        heap.offer(val);
        if(heap.size()>_k) heap.poll();//仍然要始终维持堆的大小为k
        return heap.peek();
    }
}

总结

  • 本题使用PriorityQueue的方法与以往不同:如果heap大小超过了k,就立即将堆顶元素弹出,最后堆中剩下的就是前k大元素,这个方法的代码实现比较方便,以后可以使用这个

二、前K个高频单词

Leetcode链接

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。

解题思路

  • 先使用哈希表将单词的出现频次记录,再建立合适的堆结构,将元素放入。

代码实现及解析

java 复制代码
class Solution {
    public List<String> topKFrequent(String[] words, int k) {
        List<String> list=new ArrayList<>();
        //1.使用哈希表将单词的出现频次记录
        Map<String,Integer> hash=new HashMap<>();
        for(String str:words){
            hash.put(str,hash.getOrDefault(str,0)+1);
        }

        //2.建立合适的堆结构,将元素放入
        PriorityQueue<Map.Entry<String,Integer>> heap=new PriorityQueue<>((a,b)->{
            if(a.getValue().equals(b.getValue())){//注意用equals()
                return b.getKey().compareTo(a.getKey());//字典序(升序),相当于说"前k小",所以建大根堆
            }else{
                return a.getValue()-b.getValue();//频次不同,按频次从大到小,所以建小根堆
            }
        });
        for(Map.Entry<String,Integer> entry:hash.entrySet()){
            heap.offer(entry);
            if(heap.size()>k){
                heap.poll();
            }
        }

        //3.取出结果
        while(!heap.isEmpty()){
            list.add(heap.poll().getKey());
        }
        Collections.reverse(list);
        return list;
    }
}

总结

  • 哈希表的应用+TopK问题
  • 可以注意一下第2点Lambda表达式中的方法主体实现,分别构建了大根堆和小根堆,已经注释中解释的为什么要分别构建了大根堆和小根堆

三、数据流的中位数

Leetcode链接

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

例如 arr = 2,3,4 的中位数是 3 。

例如 arr = 2,3 的中位数是 (2 + 3) / 2 = 2.5 。

实现 MedianFinder 类:

MedianFinder() 初始化 MedianFinder 对象。

void addNum(int num) 将数据流中的整数 num 添加到数据结构中。

double findMedian() 返回到目前为止所有元素的中位数。

与实际答案相差 10-5 以内的答案将被接受。

解题思路

方法一:

  • 直接调用Collections.sort(),然后去中位数。排序的时间复杂度为O(nlogn),每次执行add()操作都要进行这样的排序。

方法二:

  • 换一个排序思路,每次add()之前,数据都是有序的,那么add()时就只需要找到num的正确的位置将其插入就行了。所以我们使用插入排序的思想,遍历数据找到插入位置,这样时间复杂度变为了O(n),但还是太大。

方法三:

  • "大、小根堆法",用两个堆(PriorityQueue)来维护数据,一个是大根堆,一个是小根堆。将有序数据分为两半,左半部分用leftHeap(大根堆)储存,右半部分用rightHeap(小根堆)来储存,元素个数为奇数时我们令leftHeap的元素比rightHeap的元素个数多1(谁多谁少都一样,处理方式换一下位置而已)。两个堆的大小一样时,leftHeap.peek() 就是中位数,leftHeap大小比rightHeap大1时,(leftHeap.peek()+rightHeap.peek())/2.0 就是中位数。
  • 在add数据时,如果num<leftHeap.peek(),此时num只能放到leftHeap中,不然就一定不符合排序顺序,同样如果num>leftHeap.peek(),此时num就只能放到rightHeap中,不然就一定不符合排序顺序(两个堆都为空时,放入leftHeap)。这样num的放入那个堆就是确定的,就无法保证leftHeap大小等于rightHeap大小或者rightHeap大小+1。我们在num入堆后分情况处理一下就行,将两个堆的大小平衡手动再调整过来。

代码实现及解析

java 复制代码
class MedianFinder {
    public PriorityQueue<Integer> leftHeap;
    public PriorityQueue<Integer> rightHeap;
    
    public MedianFinder() {
        leftHeap=new PriorityQueue<>((a,b)->b-a);//大根堆
        rightHeap=new PriorityQueue<>((a,b)->a-b);//小根堆
    }
    
    public void addNum(int num) {
        if(leftHeap.size()==rightHeap.size()){//add之前,if两个堆的大小相等
            if(leftHeap.isEmpty()||num<=leftHeap.peek()){//此时num只能放到leftHeap中,不然就一定不符合排序顺序
                leftHeap.offer(num);
            }else{//num>leftHeap.peek(),此时num就只能放到rightHeap中,不然就一定不符合排序顺序
                rightHeap.offer(num);
                leftHeap.offer(rightHeap.poll());//为了维持leftHeap大小始终等于rightHeap,或比rightHeap大1
            }
        }else{//add之前,if两个堆的大小不相等(也就是leftHeap==rightHeap+1)
            if(num<=leftHeap.peek()){
                leftHeap.offer(num);
                rightHeap.offer(leftHeap.poll());//leftHeap的大小最多只能比rightHeap大1
                //执行之后,leftHeap大小==rightHeap大小
            }else{
                rightHeap.offer(num);
                //执行之后,leftHeap大小==rightHeap大小
            }
        }
        
    }
    
    public double findMedian() {
        //取中位数
        if(leftHeap.size()==rightHeap.size()){
            return (leftHeap.peek()+rightHeap.peek())/2.0;
        }else{
            return leftHeap.peek();
        }
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

总结

  • 复习解题思路和代码实现及解析
相关推荐
aqiu11111114 分钟前
【算法日记 13】LeetCode 49. 字母异位词分组:哈希表的进阶“降维打击”
算法·leetcode·散列表
KaMeidebaby23 分钟前
卡梅德生物技术快报|纳米抗体技术全套实操流程:AFB1 全合成文库淘选 + 分子对接定点突变参数手册
人工智能·python·tcp/ip·算法·机器学习
巴糖1 小时前
从做早餐理解 DAG 与拓扑排序:任务依赖图的核心逻辑
算法
2601_962851741 小时前
计算机毕业设计之基于YOLOV10的低光照目标检测算法研究与实现
大数据·算法·yolo·目标检测·信息可视化·cnn·课程设计
magicwt1 小时前
快手自动出价论文GAVE阅读笔记
算法
智者知已应修善业1 小时前
【P12159蓝桥杯数组翻转】2025-4-24
c语言·c++·经验分享·笔记·算法·蓝桥杯
小巧的砖头2 小时前
使用SVN+CruiseControl+ANT实现持续集成之一
学习·算法
人道领域2 小时前
【LeetCode刷题日记】贪心算法理论与实战:455.分发饼干最优解
java·开发语言·数据结构·算法·leetcode·贪心算法
小O的算法实验室3 小时前
2026年ACM TCH,动态救护车路径优化:结合 K-means 聚类与多目标粒子群算法两阶段策略
算法
在书中成长3 小时前
HarmonyOS 小游戏《对战五子棋》开发第8篇 - GomokuEngine核心引擎设计(三):五子连珠判定算法
算法·华为·harmonyos