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

文章目录

一、数据流中的第 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();
 */

总结

  • 复习解题思路和代码实现及解析
相关推荐
小欣加油1 小时前
leetcode56 合并区间
c++·算法·leetcode·职场和发展
lqqjuly1 小时前
前沿算法深度解析(二)
人工智能·算法·机器学习
徐小夕3 小时前
万字长文!千万级文档 RAG 知识库系统落地实践
前端·算法·github
akunkuntaimei3 小时前
2026年高考数学各省真题及答案(完整版)
算法·高考
Hello:CodeWorld4 小时前
C 风格变参 vs C++ 变参模板:核心区别与选型指南
c语言·c++·算法
8Qi85 小时前
LeetCode 516:最长回文子序列
算法·leetcode·职场和发展·动态规划
youngerwang6 小时前
【从搬运工到协处理器:网卡芯片架构、算法、验证与边缘演进深度剖析】
网络·算法·架构·芯片
KaMeidebaby6 小时前
卡梅德生物技术快报|纯化重组蛋白实操详解
人工智能·python·tcp/ip·算法·机器学习
手写码匠7 小时前
从零实现 Prompt 工程引擎:结构化提示、自动优化与多轮自省体系
人工智能·深度学习·算法·aigc
无限码力7 小时前
阿里算法岗 0530笔试真题 - 多约束条件下的元素匹配统计
算法·阿里笔试真题·阿里机试真题·阿里算法岗笔试