目录

  • PriorityQueue有几个需要注意的点:
  • 重写比较器的方法
  • 应用题目
    • LeetCode 1845. 座位预约管理系统
    • LeetCode 215. 数组中的第 K 个最大元素(同剑指 Offer II 076. 数组中的第 k 大的数字)
    • LeetCode 703. 数据流中的第 K 大元素(同剑指 Offer II 059. 数据流的第 K 大数值)
    • LeetCode 295. 数据流的中位数(同剑指 Offer 41. 数据流中的中位数)
    • LeetCode 23. 合并 K 个升序链表(同剑指 Offer II 078. 合并排序链表)
    • LeetCode 1834. 单线程 CPU
    • LeetCode 239. 滑动窗口最大值

Java中PriorityQueue底层通过二叉小顶堆实现,可以用一棵完全二叉树表示。

优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列默认每次取最小元素,C++的优先队列默认每次取最大元素)。这里牵涉到了大小关系,元素大小的评判可以通过元素本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator,类似于C++的仿函数)。

如果排列的元素不是基本类型,而是自定义的对象(比如学生对象,按年龄排序,节点类型,按节点值排序),这种就需要重写比较器Comparator。或者想要每次从队头取出的都是最大的那个元素,也需要在创建PriorityQueue对象的时候重写作为参数的比较器Comparator。

PriorityQueue有几个需要注意的点:

  • 不允许加入NULL对象
  • 添加到PriorityQueue的对象必须具有可比性
  • 比较器Comparator可用于队列中对象的自定义排序(升序/降序,自定义参与比较的是对象的哪个变量)
  • PriorityQueue是一个无限制的队列,并且动态增长。默认初始容量’11’可以使用相应构造函数中的initialCapacity参数覆盖
  • 如果存在多个具有相同优先级的对象,则它可以随机轮询其中任何一个
  • PriorityQueue 不是线程安全的。PriorityBlockingQueue在并发环境中使用
  • 它为add/offer和remove/poll方法提供了O(log(n))时间

重写比较器的方法

第一种是容易理解的,比如对链表节点ListNode类型按val值进行升序排序:

PriorityQueue<ListNode> priorityQueue=new PriorityQueue<>(new Comparator<ListNode>(){//因为需要对ListNode进行排序,需要重写排序规则@Overridepublic int compare(ListNode node1,ListNode node2){return node1.val-node2.val;//按val值升序规则排序}});

第二种是借助lambda表达式:

PriorityQueue<ListNode> priorityQueue=new PriorityQueue<>((node1,node2)->node1.val-node2.val);

应用题目

LeetCode 1845. 座位预约管理系统

原题链接

思路:
reverse与unreverse操作可能会比较频繁,需要较好的效率。简单的思路是每次加入座位后都对编号进行从小到大的排序,但是这样时间复杂度比较高,从题目要求reverse每次都返回最小编号,可以想到二叉堆(优先级队列)的实现,其默认是最小堆。

代码如下:

class SeatManager {PriorityQueue<Integer> pq=new PriorityQueue<>();//初始化public SeatManager(int n) {for(int i=1;i<=n;i++){pq.offer(i);}}//弹出最小编号public int reserve() {return pq.poll();}public void unreserve(int seatNumber) {pq.offer(seatNumber);}
}/*** Your SeatManager object will be instantiated and called as such:* SeatManager obj = new SeatManager(n);* int param_1 = obj.reserve();* obj.unreserve(seatNumber);*/

LeetCode 215. 数组中的第 K 个最大元素(同剑指 Offer II 076. 数组中的第 k 大的数字)

原题链接

只要维护一个大小为k的最小堆,遍历一遍数组,不断把遍历到的元素加入最小堆,当元素个数超过k时,将堆顶元素弹出,这样就可以把当前堆内最小的元素都弹出,最后剩下k个最大的元素,而堆顶元素就是第k大的数。

代码如下:

//最小堆方案
class Solution {public int findKthLargest(int[] nums, int k) {PriorityQueue<Integer> pq=new PriorityQueue<>();for(int i=0;i<nums.length;i++){//所有元素过一遍最小堆pq.offer(nums[i]);//但是元素超过k个需要把顶部元素取出,保持堆大小为kif(i>=k)pq.poll();}return pq.peek();}
}

LeetCode 703. 数据流中的第 K 大元素(同剑指 Offer II 059. 数据流的第 K 大数值)

原题链接

这题与前一题是差不多的思路,只是要求add操作完要返回当前第k大的。

代码如下:

class KthLargest {private PriorityQueue<Integer> pq=new PriorityQueue<>();private int k;public KthLargest(int k, int[] nums) {for(int i=0;i<nums.length;i++){pq.offer(nums[i]);if(i>=k)pq.poll();}this.k=k;}public int add(int val) {pq.offer(val);if(pq.size()>k)pq.poll();return pq.peek();}
}/*** Your KthLargest object will be instantiated and called as such:* KthLargest obj = new KthLargest(k, nums);* int param_1 = obj.add(val);*/

LeetCode 295. 数据流的中位数(同剑指 Offer 41. 数据流中的中位数)

原题链接

用一个大顶堆和一个小顶堆维护一堆数据的中位数。

class MedianFinder {//假设nums是一个排序好的数组,那么小顶堆元素就是nums的后半截,堆顶是nums的中间数//大顶堆元素就是nums的前半截,堆顶是nums的中间数//插入需要保证大顶堆元素个数大于等于小顶堆元素个数,且最多多1个//这样在找中位数时,只要元素个数不同,中位数就是大顶堆堆顶private PriorityQueue<Integer> small;//小顶堆private PriorityQueue<Integer> large;//大顶堆public MedianFinder() {small=new PriorityQueue<>();large=new PriorityQueue<>((n1,n2)->{return n2-n1;});}//插入num需要保证大小顶堆的元素大小关系//num与大顶堆(小于等于中位数)的关系作为判断//如果num<=large.peek(),应该插入大顶堆//如果此时大顶堆元素比小顶堆元素多2,就要把大顶堆堆顶元素插入小顶堆//如果num>large.peek(),应该插入小顶堆//public void addNum(int num) {if(large.size()==0)large.offer(num);else if(num<=large.peek()){large.offer(num);if(large.size()>small.size()+1)small.offer(large.poll());}else{small.offer(num);if(large.size()<small.size())large.offer(small.poll());}}public double findMedian() {if(small.size()==large.size())return (double)(small.peek()+large.peek())/2.0;else return (double)large.peek();}
}/*** Your MedianFinder object will be instantiated and called as such:* MedianFinder obj = new MedianFinder();* obj.addNum(num);* double param_2 = obj.findMedian();*/

LeetCode 23. 合并 K 个升序链表(同剑指 Offer II 078. 合并排序链表)

原题链接

2023.06.10 二刷

代码如下:

class Solution {public ListNode mergeKLists(ListNode[] lists) {/**PriorityQueue<ListNode> priorityQueue=new PriorityQueue<>(new Comparator<ListNode>(){//因为需要对ListNode进行排序,需要重写排序规则@Overridepublic int compare(ListNode node1,ListNode node2){return node1.val-node2.val;//按val值升序规则排序}});*/PriorityQueue<ListNode> priorityQueue=new PriorityQueue<>((node1,node2)->node1.val-node2.val);//遍历链表数组每一条链表(实际上就是每条链表的头结点)for(ListNode node:lists){//当前链表可能是空的,优先级队列不能添加null元素if(node!=null)priorityQueue.offer(node);}//用一个新链表承接节点ListNode dummy=new ListNode(-1);ListNode cur=dummy;while(!priorityQueue.isEmpty()){ListNode node=priorityQueue.poll();//取出优先级队列中最小的//取一个出来,就要放一个进去if(node.next!=null)priorityQueue.offer(node.next);cur.next=node;//把node接到新链表后面cur=cur.next;//新链表指针向前移动}return dummy.next;}
}

LeetCode 1834. 单线程 CPU

原题链接

思路见代码中注释。

代码如下:

class Solution {public int[] getOrder(int[][] tasks) {int n=tasks.length;//任务数量/**第一步 *///三元数组,将task的编号记录作为第三个元素,防止排序后丢失编号顺序int[][] newTasks=new int[n][3];for(int i=0;i<n;i++){newTasks[i][0]=tasks[i][0];newTasks[i][1]=tasks[i][1];newTasks[i][2]=i;//记录任务编号}/**第二步 *///根据任务入队时间升序排序Arrays.sort(newTasks,(o1,o2)->o1[0]-o2[0]);/**第三步 *///优先队列,重写排序规则//按执行时间升序排序,执行时间相同按任务编号升序PriorityQueue<int[]> pq=new PriorityQueue<>(new Comparator<int[]>(){public int compare(int[] o1,int[] o2){//执行时间不同时,优先按照执行时间升序排序if(o1[1]!=o2[1])return o1[1]-o2[1];//如果执行时间相同,就按任务编号升序return o1[2]-o2[2];}});/**最后一步--遍历处理 */int[] res=new int[n];//存储结果(任务编号顺序)int time=0;//记录任务执行时间线int resIndex=0;//执行完的任务数量,用于遍历res数组,res[resIndex]存储任务编号int tIndex=0;//用于遍历newTasks数组//用resIndex遍历while(resIndex<n){/**进入优先级队列的逻辑 *///安排任务进入优先队列//要保证还有任务可以安排(tIndex<n)//需要被安排的任务一定是进入序列的时间小于等于当前时间线的while(tIndex<n&&newTasks[tIndex][0]<=time){pq.offer(newTasks[tIndex]);tIndex++;//newTasks的指针前移指向下一个待处理任务}/**执行优先级队列里的任务的逻辑 *///如果优先队列为空,说明没有任务等待//时间线直接跳到下一个任务进入任务序列的时间if(pq.isEmpty()){time=newTasks[tIndex][0];}else{//队列不为空,就要执行堆顶任务int[] cur=pq.poll();//记录堆顶任务三元组res[resIndex++]=cur[2];//记录任务编号,并且索引前进time+=cur[1];//时间线快跳到任务执行完成}}return res;}
}

LeetCode 239. 滑动窗口最大值

原题链接

2023.06.01 三刷

最大堆思路:
将最大堆–二元组(num,index)排序规则设置为按num降序,num相同则按下标升序(但是优先队列的默认排序规则就是升序,所以num相同时下标会默认按升序来,不用特别写)

何时弹出二叉堆元素?

正常思维肯定是当堆元素个数超过k的时候弹出,但是这题当堆元素个数超过k,弹出的堆顶元素可能不是最左边窗口边界的元素。用i遍历剩下的元素,i-k+1就是窗口左边界,只要看堆顶元素的下标是不是小于这时候的左边界,如果小于,说明堆顶元素不在窗口内,可以弹出,这样一直判断直到堆顶元素是窗口内的,就可以给res赋值。

代码如下:

//最大堆,时间O(nlogn),空间O(n)
class Solution {public int[] maxSlidingWindow(int[] nums, int k) {//设置优先级队列排序规则PriorityQueue<int[]> pq=new PriorityQueue<>(new Comparator<int[]>(){public int compare(int[] o1,int[] o2){return o1[0]!=o2[0] ? o2[0]-o1[0] : o2[1]-o1[1];}});//初始化二叉堆(存入前k个元素)for(int i=0;i<k;i++){pq.offer(new int[]{nums[i],i});}int n=nums.length;int[] res=new int[n-k+1];//最后会有n-k+1个窗口res[0]=pq.peek()[0];//先把最开始堆顶元素填入//遍历剩下的数组元素for(int i=k;i<n;i++){pq.offer(new int[]{nums[i],i});//当堆顶元素(最大的)不在当前窗口内,就弹出while(pq.peek()[1]<=i-k){pq.poll();}//出while能保证当前堆顶元素一定在窗口内,就是最大值res[i-k+1]=pq.peek()[0];}return res;}
}

Java PriorityQueue(优先级队列/二叉堆)的使用及题目应用相关推荐

  1. 二叉堆的优先队列基本原理及实现

    原理: 传统的队列是先进先出的数据结构,队列的重要变种称为优先级队列 二叉堆常见的遍体:最小堆(其中最小的键在前面)和最大堆(其中最大的键值总是在前面) 代码实现

  2. 《恋上数据结构第1季》二叉堆实现优先级队列

    优先级队列(Priority Queue) 优先级队列简介 优先队列的底层实现 二叉堆实现优先级队列源码 测试代码 数据结构与算法笔记目录:<恋上数据结构> 笔记目录 想加深 Java 基 ...

  3. 基于动态数组建立二叉堆、批量建堆-JAVA

    文章目录 1.堆的数据结构 2.添加 3.删除 4.代码实现 1.首先定义接口 2.写实现类 5.如何批量建堆 1.堆的数据结构 如果任意节点的值总是≥子节点的值,称为:最大堆.大根堆.大顶堆 如果任 ...

  4. 二叉堆(TopK问题,优先级队列)

    目录 实现一个大根堆 优先级队列 Comparable和Compator区别 compareTo方法 TopK问题 TopK问题常见题型为求最大(最小)的K个值. 我们一般拿堆来解决. 堆:二叉堆首先 ...

  5. 二叉堆详解实现优先级队列

    二叉堆详解实现优先级队列 文章目录 二叉堆详解实现优先级队列 一.二叉堆概览 二.优先级队列概览 三.实现 swim 和 sink 四.实现 delMax 和 insert 五.最后总结 二叉堆(Bi ...

  6. java实现二叉堆,数据结构基础篇-二叉堆

    二叉堆分为两种,最大堆和最小堆,我们只讨论最小堆的性质,最大堆具有相同的原理. 最小堆是一种符合下面两个特性的树形结构: 最小堆是一颗完全二叉树,即最小堆的每个节点要么没有子节点,要么只有一个左子节点 ...

  7. 数据结构之优先队列--二叉堆(Java实现)

    前言 数据结构队列的学习中,我们知道队列是先进先出的.任务被提交到队列中,按照先进先出的原则 对各个任务进行处理.不过在现实的情况下,任务通常有着优先级的概念,例如短任务.管理员的操作 应该优先执行. ...

  8. Java【优先级队列】详细图解 / 模拟实现 + 【PriorityQueue】常用方法介绍

    文章目录 一.什么是优先级队列 二.模拟实现 1, 实现堆的基本操作 1.1, 创建堆 1.2.1, 向下调整 1.2, 堆的插入 1.2.1, 向上调整 1.2, 堆的删除 2, 实现优先级队列 2 ...

  9. java优先队列二叉_二叉堆与Java中的优先队列

    之前在A*算法演示程序的编码过程中,发现javaScript并没有原生的优先队列,于是去Java中找到了PriorityQueue类,研究了一下源码.Java中的优先队列基于最小二叉堆实现.最小二叉堆 ...

最新文章

  1. 树莓派(Raspberry Pi 3) centos7使用yum命令报错File /usr/bin/yum, line 30 except KeyboardInterrupt, e:...
  2. python常用的字串格式化选项
  3. PyQt中从RAM新建QIcon对象 / Create a QIcon from binary data
  4. [导入]使用SqlCommand对象执行存储过程
  5. ubuntu14.04 安装pidgin-lwqq
  6. html上传图片至数据库,Django 图片上传到数据库 并调用显示
  7. GPS各种地图坐标系转换(转载)
  8. 【javascript】手写一个webpack plugin
  9. Atitit 理财之道---支出大骗局分析与防范
  10. 本科毕业查重过程分享与经验
  11. gcode 解析入门1-1
  12. JAVA作业 随机抽取
  13. CentOs7 docker部署face_recognition
  14. 查看笔记本预装系统的产品密钥
  15. 冉宝的每日一题--8月13日
  16. TextClock 24小时制
  17. 头歌 共享单车大数据项目数据分析
  18. 强制重启Linux系统的几种方法
  19. 2020计算机数电实验二
  20. BufferedReader读文件

热门文章

  1. ios 图片裁剪框架_iOS图片裁剪器 – RSKImageCropper
  2. MSDN网站新版亮相
  3. 计算机用管理员账户登录,W10电脑系统管理员账户不见了如何用Administrator登录...
  4. 不同平台的OCR文字识别工具整理
  5. java线程间通讯的几种方式
  6. Win10+Anaconda+Pytorch+PyCharm 环境搭建
  7. 企业网站制作模板,使企业网站建设更加简单
  8. 写一个递归方法,输入一个非负整数,返回组成它的数字之和.
  9. AI技术用于测试领域预研阶段
  10. 用matlab画OCC控制电路,基于单周期控制的Boost型APFC电路设计及仿真