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

文章目录

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

总结

  • 复习解题思路和代码实现及解析
相关推荐
无尽的罚坐人生2 小时前
hot 100 35. 搜索插入位置
数据结构·算法·leetcode·二分查找
葳_人生_蕤2 小时前
力扣Hot100——234.回文链表
算法·leetcode·链表
自信150413057592 小时前
数据结构之实现链式结构二叉树
c语言·数据结构·算法
Barkamin2 小时前
堆排序简单实现
java·数据结构·算法·排序算法
迈巴赫车主2 小时前
天梯赛 L2-004 这是二叉搜索树吗?java
java·开发语言·数据结构·算法·天梯赛
沐苏瑶2 小时前
Java 数据结构精讲:二叉树遍历算法与底层实现剖析
数据结构·算法
董董灿是个攻城狮3 小时前
大模型连载8:词向量如何表示近义词?
人工智能·python·算法·机器学习
Jasmine_llq3 小时前
《B4001 [GESP202406 一级] 立方数》
算法·单输入处理·整数算术运算·立方数枚举验证算法(核心逻辑)·循环终止优化算法·状态标记算法·三元运算符输出
芸忻3 小时前
day 13 第六章 二叉树 part01代码随想录算法训练营71期
数据结构·算法