LeetCode刷题笔记 标准模板库巧解算法题 优先队列

article/2025/9/15 13:24:21

优先队列简介

​ 优先队列(priority queue)可以在 O(1) 时间内获得最大值,并且可以在 O(log n) 时间内取出最大值或插入任意值。

​ 优先队列常常用堆(heap)来实现。堆是一个完全二叉树,其每个节点的值总是大于等于子节点的值。实际实现堆时,我们通常用一个数组而不是用指针建立一个树。这是因为堆是完全二叉树,所以用数组表示时,位置 i 的节点的父节点位置一定为 i/2,而它的两个子节点的位置又一定分别为 2i 和 2i+1。

​ 以下是堆的实现方法,其中最核心的两个操作是上浮和下沉:如果一个节点比父节点大,那么需要交换这个两个节点;交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作,我们称之为上浮;类似地,如果一个节点比父节小,也需要不断地向下进行比较和交换操作,我们称之为下沉。如果一个节点有两个子节点,我们总是交换最大的子节点。

vector<int> heap;
// 获得最大值
void top() {return heap[0];
}
// 插入任意值:把新的数字放在最后一位,然后上浮
void push(int k) {heap.push_back(k);swim(heap.size() - 1);
}
// 删除最大值:把最后一个数字挪到开头,然后下沉
void pop() {heap[0] = heap.back();heap.pop_back();sink(0);
}
// 上浮
void swim(int pos) {while (pos > 1 && heap[pos/2] < heap[pos])) {swap(heap[pos/2], heap[pos]);pos /= 2;}
}
// 下沉
void sink(int pos) {while (2 * pos <= N) {int i = 2 * pos;if (i < N && heap[i] < heap[i+1]){++i;}if (heap[pos] >= heap[i]){break;} swap(heap[pos], heap[i]);pos = i;}
}

​ 通过将算法中的大于号和小于号互换,我们也可以得到一个快速获得最小值的优先队列。

​ 另外,正如我们在 STL 章节提到的那样,如果我们需要在维持大小关系的同时,还需要支持查找任意值、删除任意值、维护所有数字的大小关系等操作,可以考虑使用 set 或 map 来代替优先队列。

23. 合并K个升序链表

给定一个链表数组,每个链表都已经按升序排列。请将所有链表合并到一个升序链表中,返回合并后的链表。

输入是一个一维数组,每个位置存储链表的头节点;输出是一条链表。

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组:[1->4->5, 1->3->4, 2->6] 将它们合并到一个有序链表中得到:1->1->2->3->4->4->5->6

解析:

​ 本题我们可以采用STL中的容器适配器 priority_queue,把所有的链表存储在一个优先队列中,每次提取所有链表头部节点值最小的那个节点,直到所有链表都被提取完为止。

​ 需要注意的是 priority_queue 默认的元素比较方法是less<T>,即默认为最大值元素在前面的最大堆,维持着递增关系。如果我们想要获取最小的节点值,则需要实现一个最小堆,因此比较函数应该维持递减关系。实现侧策略就是使用函数对象,自定义 priority_queue 的元素比较方法,在该函数对象中重载 operator() ,使用大于号而不是等增关系时的小于号进行比较。

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/struct MyCompare{bool operator() (ListNode* a, ListNode* b){return a->val > b->val;}
};class Solution {
public:ListNode* mergeKLists(vector<ListNode*>& lists) {if(lists.empty()) return nullptr;// 自定义优先队列的元素比较方法 MyComparepriority_queue<ListNode*, vector<ListNode*>, MyCompare> pq;// 将lists都压入到优先队列中,保持递减关系for(const auto list: lists){if(list){pq.push(list);}}// 每次取出所有链表中头部节点最小的那个节点加入结果链表ListNode head; ListNode* tail = &head;while(!pq.empty()){// 取出所有链表中的最小头节点并加入 结果链表tail->next = pq.top();pq.pop();tail = tail->next;// 加入后,将其 next 节点作为当前链表的新头节点加入优先队列if(tail->next){pq.push(tail->next);}}return head.next;}
};

本题也可以采用归并排序的思想,将链表两两合并。

根据分治策略,首先要将 k 个链表分割,使用递归的方法将链表分割为两两一组。然后将在同一个组的链表合并。

合并两个有序链表可以通过如下操作实现:

  • 首先需要一个变量 head 来保存合并之后链表的头部,在整个链表合并完之后,返回 head 的下一位置即可。
  • 需要一个指针 tail 来记录下一个插入位置的前一个位置,以及两个指针 aPtr 和 bPtr 来记录 a 和 b 未合并部分的第一位,即通过尾插法构建新链表
  • 当 aPtr 和 bPtr 都不为空的时候,取 val 熟悉较小的合并;如果 aPtr 为空,则把整个 bPtr 以及后面的元素全部合并;bPtr 为空时同理。
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* mergeTowList(ListNode* a, ListNode* b){if(!a || !b) return a?a:b;// 变量 head 来保存合并之后链表的头部ListNode head;ListNode* tail = &head;ListNode* aPtr = a;ListNode* bPtr = b;while(aPtr && bPtr){if(aPtr->val <= bPtr->val){tail->next = aPtr;aPtr = aPtr->next;}else{tail->next = bPtr;bPtr = bPtr->next;}tail = tail->next;}// 循环结束 aPtr 和 bPtr 其中一个为空,直接将非空的添加到链表if(aPtr){tail->next = aPtr;}else{tail->next = bPtr;}return head.next;}ListNode* merge(vector<ListNode*>& lists, int lsh, int rsh){if(lsh == rsh){return lists[lsh];}if(lsh > rsh){return nullptr;}int mid = (lsh + rsh) >> 1;ListNode* lshList = merge(lists,lsh,mid);ListNode* rshList = merge(lists,mid+1,rsh);return mergeTowList(lshList,rshList);}ListNode* mergeKLists(vector<ListNode*>& lists) {return merge(lists,0,lists.size()-1);}
};

218 天际线问题

给定建筑物的起止位置和高度,返回建筑物轮廓(天际线)的拐点。

输入是一个二维整数数组,表示每个建筑物的 [左端, 右端, 高度];输出是一个二维整数数组,表示每个拐点的横纵坐标。

在这里插入图片描述

输入:buildings = [[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]]
输出:[[2,10],[3,15],[7,12],[12,0],[15,10],[20,8],[24,0]]
解释:图 A 显示输入的所有建筑物的位置和高度,图 B 显示由这些建筑物形成的天际线。图 B 中的红点表示输出列表中的关键点。

解析:

本题没搞懂,有待再理解

​ 我们可以使用优先队列储存每个建筑物的高度和右端(这里使用 pair,其默认比较函数是先比较第一个值,如果相等则再比较第二个值),从而获取目前会拔高天际线、且妨碍到前一个建筑物(的右端端点)的下一个建筑物。

class Solution {
public:vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {vector<vector<int>> ans;priority_queue<pair<int,int>> heap;int i = 0;int len = buildings.size();int cur_x, cur_h;while(i<len || !heap.empty()){if(heap.empty() || i<len && buildings[i][0] <= heap.top().second){cur_x = buildings[i][0];while(i<len && cur_x==buildings[i][0]){heap.emplace(buildings[i][2], buildings[i][1]);++i;}}else{cur_x = heap.top().second;while(!heap.empty() && cur_x >= heap.top().second){heap.pop();}}cur_h = (heap.empty())?0:heap.top().first;if(ans.empty() || cur_h != ans.back()[1]){ans.push_back({cur_x,cur_h});}}return ans;}
};

870 优势洗牌

给定两个大小相等的数组 A 和 B,A 相对于 B 的优势可以用满足 A[i] > B[i] 的索引 i 的数目来描述。

返回 A 的任意排列,使其相对于 B 的优势最大化。

输入:A = [12,24,8,32], B = [13,25,32,11]
输出:[24,32,8,12]

解析:

​ 本题采用田忌赛马的策略,如果 A 中有元素比 B 的大,那么用最大的那个值取对应B中的元素;如果A中没有元素比当前B大,那么用最小的 A 对应该元素。

​ 我们采用优先队列保存 B 中的元素值和原先位置,并维持递减顺序。将 A 中元素按递增排序,然后遍历优先队列中的 B 元素。因为B采用优先队列保存,所以取出的总是当前B的最大元素,如果 A 中存在比该元素还大的值,则取 A 的当前最大值即排序后数组末尾元素放在该元素的原先位置;不存在就用 A 中的最小元素即排序后数组第一个元素放在该元素的原先位置。

​ 例如A = [12,24,8,32], B = [13,25,32,11],对A排序,对B用优先队列存储元素值和原先位置有A = [8,12,24,32], B = [{32,2},{25,1},{13,0},{11,3}],结果形成过程如下:

sort_Apriority_queue_Bresult_A
[8,12,24,32][{32,2},{25,1},{13,0},{11,3}][0,0,0,0]
[12,24,32][{25,1},{13,0},{11,3}][0,0,8,0]
[12,24][{13,0},{11,3}][0,32,8,0]
[12][{11,3}][24,32,8,0]
[][{}][24,32,8,12]
class Solution {
public:vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {vector<int> ans(nums1.size());// A 排序sort(nums1.begin(),nums1.end());// B 用优先队列保存priority_queue<pair<int,int>> pq;for(int i=0;i<nums2.size();++i){pq.push(make_pair(nums2[i],i));}// 逐个比较优先队列中的元素,根据大取大、小取小的原则洗牌A的元素int head = 0, tail = nums1.size()-1;while(!pq.empty()){int num = pq.top().first;int index = pq.top().second;pq.pop();if(nums1[tail]>num){ans[index] = nums1[tail--];}else{ans[index] = nums1[head++];}}return ans;}
};

参考资料

LeetCode 101:和你一起轻松刷题(C++) 第 11 章 妙用数据结构


http://chatgpt.dhexx.cn/article/WXNLRegm.shtml

相关文章

Python数据结构与算法(3.4)——队列相关应用与习题

Python数据结构与算法(3.4)——队列相关应用与习题 0. 学习目标1. 使用两个栈实现一个队列2. 使用两个队列实现一个栈3. 栈中元素连续性判断4. 重新排列队列中元素顺序5. 反转队列中前 m 个元素的顺序相关链接0. 学习目标 我们已经学习了队列的相关概念以及其实现,同时也了…

第十七章 优先队列优化Dijkstra算法

第十七章 优先队列优化Dijkstra算法 一、普通dijkstra算法的缺陷1、选出最小距离的过程&#xff1a;2、松弛所有点的过程&#xff1a; 二、如何优化1、代码模板&#xff08;1&#xff09;问题&#xff1a;&#xff08;2&#xff09;模板&#xff1a; 2、详细解读 三、优化分析1…

【自顶向下模块化编程】C语言实现多级反馈队列调度算法

自顶向下-多级反馈队列 多级反馈队列算法算法原理算法描述题目摘要 自顶向下模块化设计整体框架具体实现GeneratorSchedulerExecutor 整体代码实现 总结及心得总结心得 多级反馈队列算法 多级反馈队列调度算法是一种CPU处理机调度算法&#xff0c;UNIX操作系统采取的便是这种调…

[算法] 栈和队列

欢迎来到老胡的算法解题思路&#xff0c;本文章主要使用的语言为java&#xff0c;使用的题型为力扣算法题&#xff0c;基于这一篇文章&#xff0c;我将为你介绍栈和队列的基础知识和栈和队列的题型&#xff0c;喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&#xff01; 目…

十道经典面试算法真题详解

前言 分享一下 腾讯常考的十道算法题&#xff08;真题&#xff09;。在金三银四&#xff0c;希望对大家有帮助呀。 重排链表 最长递增子序列 环形链表 反转链表 最长回文子串 全排列 LRU 缓存 合并K个升序链表 无重复字符的最长子串 删除链表的倒数第 N 个结点 1. …

队列相关习题

1.已知循环队列存储在一维数组A0…n-1]中&#xff0c;且队列非空时front和rear分别指向队头元素和队尾元素。若初始时队列为空&#xff0c;且要求第一个进入队列的元素存储在A[0]处&#xff0c;则初始时front和rear的值分别是( )。 A.0&#xff0c;0 B. 0&#xff0c;n-1 C. n-…

java算法面试题_Java算法面试题汇总

原标题&#xff1a;Java算法面试题汇总 1. 字符串 如果IDE没有代码自动补全功能&#xff0c;所以你应该记住下面的这些方法。 toCharArray() // 获得字符串对应的char数组 Arrays.sort() // 数组排序 Arrays.toString(char[] a) // 数组转成字符串 charAt(int x) // 获得某个索…

详解单调队列算法

前言 嘿!彩蛋!感觉有帮助就三连呗! 如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。 在上一篇文章中,我们介绍了「单调栈」这一最常考察的线性数据结构。而今天我们将继续沿着这个思路,介绍另…

栈和队列相关经典算法题总结(数据结构+C语言)

我们这里针对栈和队列的一些经典算法题做详细讲解: 1.括号匹配问题. 2.用队列实现栈. 3.用栈实现队列. 4.设计循环队列. 一.详细讲解如下: 1.括号匹配问题.(如下图) 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &am…

qt使用消息队列服务器,qt代码实现消息队列通信

qt代码实现消息队列通信 内容精选 换一换 HBase 1.X版本在RPC流程中&#xff0c;多个数据通信线程会争抢同一个缓存Buffer队列&#xff0c;代码以lock重入锁实现线程安全&#xff0c;锁抢占严重&#xff0c;导致HBase不能充分发挥CPU多核的能力。HBase 1.X版本的RPC通信机制中B…

消息队列MQ常见面试题

面试官在面试候选人时&#xff0c;如果发现候选人的简历中写了在项目中使用了 MQ 技术&#xff08;如 Kafka、RabbitMQ、RocketMQ&#xff09;&#xff0c;基本都会抛出一个问题&#xff1a;在使用 MQ 的时候&#xff0c;怎么确保消息 100% 不丢失&#xff1f; 这个问题在实际…

RabbitMQ消息队列常见面试题总结

1、什么是消息队列&#xff1a; 1.1、消息队列的优点&#xff1a; &#xff08;1&#xff09;解耦&#xff1a;将系统按照不同的业务功能拆分出来&#xff0c;消息生产者只管把消息发布到 MQ 中而不用管谁来取&#xff0c;消息消费者只管从 MQ 中取消息而不管是谁发布的。消息…

【消息队列】面试题及答案整理

消息队列面试题 为什么要使用消息队列/消息队列的应用场景使用了消息队列会有什么缺点如何保证消息队列是高可用的RocketMQ是如何保证消息队列是高可用的 如何保证消息不被重复消费/如何保证消息消费的幂等性如何保证消费的可靠性传输RocketMQ如何保证消费的可靠性传输RabbitMQ…

JAVA——快速排序(详细)

JAVA快速排序的实现 快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高&#xff0c;因此经常被采用&#xff0c;再加上快速排序思想----分治法也确实实用&#xff0c;因此很多软件公司的笔试面试&#xff0c;包括像腾讯&#xff0c;微软等知名IT公司都喜欢考这个&…

快速排序算法(java实现)

基本思想 快速排序是一种采用分治法解决问题的一个典型应用&#xff0c;也是冒泡排序的一种改进。它的基本思想是&#xff0c;通过一轮排序将待排记录分割成独立的两部分&#xff0c;其中一部分均比另一部分小&#xff0c;则可分别对这两部分继续进行排序&#xff0c;已达到整…

java快速排序(含快速排序代码)

目录 一&#xff1a;快速排序思想 二&#xff1a;快速排序代码&#xff08;pivot一定时先和arrays【r】先比较&#xff09; 三&#xff1a;结果 一&#xff1a;快速排序思想 假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准…

快速排序 Java 实现

概念 快速排序&#xff08;Quicksort&#xff09;是对冒泡排序的一种改进。 参考: [数据结构与算法(Kotlin语言)]1.冒泡排序&#xff08;Bubble Sort&#xff09; 快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略&#xff0c;通常称其为分治法(…

java快速排序详解

文章目录 一、快排原理二、实例操作三、实战代码四、总结 一、快排原理 从待排序区间选择一个数&#xff0c;作为基准值&#xff08;pivot&#xff09;&#xff1b;遍历整个待排序区间&#xff0c;将比基准值小的&#xff08;可等于&#xff09;放到基准值左边&#xff0c;将比…

快速排序Java

基本思想 快速排序的基本思想&#xff1a;通过一趟排序将待排记录分隔成独立的两部分&#xff0c;其中一部分记录的关键字均比另一部分的关键字小&#xff0c;则可分别对这两部分记录继续进行排序&#xff0c;以达到整个序列有序。 算法描述 快速排序使用分治法来把一个串&…

快速排序 Java模板

快速排序Java模板 详情参考 https://www.acwing.com/problem/content/787/ https://www.acwing.com/solution/content/2096/ 快速排序的整体过程&#xff0c;动态变化流程 以从小到大排序为例 选择一个目标参考值 p i v i t pivit pivit&#xff0c;通常课本上会说选择数组…