当前位置: 首页 > news >正文

020数据结构之优先队列——算法备赛

优先队列

这里的优先队列其实是官方的说法,它的底层是堆(一种二叉树结构),很多编程语言会将其封装后对外暴露,API类似队列(都有入队,出队,获取队头等操作)。

以C++中的优先队列priority_queue为例,队头总是最大(默认)或最小值。更多API详情,这里不再详述。

数据流的中位数

问题描述

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

  • 例如 arr = [2,3,4] 的中位数是 3
  • 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5

实现 MedianFinder 类:

  • MedianFinder() 初始化 MedianFinder 对象。
  • void addNum(int num) 将数据流中的整数 num 添加到数据结构中。

原题链接

思路分析

我们用两个优先队列 queMax,queMin 分别记录大于中位数的数小于等于中位数的数。当累计添加的数的数量为奇数时,queMin 中的数的数量比 queMax 多一个,此时中位数是queMin 的队头。当累计添加的数的数量为偶数时,两个优先队列中的数的数量相同,此时中位数为它们的队头的平均值。

当我们尝试添加一个数 num 到数据结构中,我们需要分情况讨论:

num≤max{queMin}⁡

此时 num 小于等于中位数,我们需要将该数添加到 queMin 中。新的中位数将小于等于原来的中位数,当queMin.size()比queMax.size()大2时需要将 queMin 中最大数移动到 queMax 中。

num>max{queMin}

此时 num 大于中位数,我们需要将该数添加到 queMax 中。新的中位数将大于等于原来的中位数,当queMax.size()比queMin.size()大时将 queMax 中最小数移动到 queMin 中。

特别地,当累计添加的数的数量为 0 时,我们将 num添加到 queMin 中。

代码

class MedianFinder {
public:priority_queue<int>queMin;  //升序,队头为最大值priority_queue<int,vector<int>,greater<int>>queMax;  //降序,队头为最小值MedianFinder() {}void addNum(int num) {if (queMin.empty() || num <= queMin.top()) {queMin.push(num);if (queMax.size() + 1 < queMin.size()) {queMax.push(queMin.top());  //将小于等于中位数队列中的最大值移动到大于中位数队列queMin.pop();}} else {queMax.push(num);if (queMax.size() > queMin.size()) {queMin.push(queMax.top());  //将大于中位数队列中的最小值移动到小于等于中位数队列queMax.pop();}}}double findMedian() {if(queMin.size()>queMax.size()) return queMin.top();return ((double)queMin.top()+(double)queMax.top())/2;}
};

最小区间

问题描述

你有 k非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。

我们定义如果 b-a < d-c 或者在 b-a == d-ca < c,则区间 [a,b][c,d] 小。

原题链接

思路分析

贪心+最小堆

给定 k 个列表,需要找到最小区间,使得每个列表都至少有一个数在该区间中。该问题可以转化为,从 k 个列表中各取一个数,使得这 k 个数中的最大值与最小值的差最小。

假设这 k 个数中的最小值是第 i 个列表中的 x,对于任意 j=i,设第 j 个列表中被选为 k 个数之一的数是 y,则为了找到最小区间,y 应该取第 j 个列表中大于等于 x 的最小的数,这是一个贪心的策略。

具体实现:

由于 k 个列表都是升序排列的,因此对每个列表维护一个指针,通过指针得到列表中的元素,指针右移之后指向的元素一定最小的大于或等于之前的元素

使用最小堆维护 k 个指针指向的元素中的最小值,同时用一个变量维护堆中元素的最大值。初始时,k 个指针都指向下标 0,最大元素即为所有列表的下标 0 位置的元素中的最大值。每次从堆中取出最小值,根据最大值和最小值计算当前区间,如果当前区间小于最小区间则用当前区间更新最小区间,然后将对应最小值列表的指针右移,将新元素加入堆中,并更新堆中元素的最大值。这样每次递增遍历地选取最小值作为区间左边界,保证了所有可能的答案都能计算到。

如果一个列表的指针超出该列表的下标范围,则说明该列表中的所有元素都被遍历过,堆中不会再有该列表中的元素,表明区间不满足要求,遍历到顶了,因此退出循环。

代码

    vector<int> smallestRange(vector<vector<int>>& nums) {int rangeLeft = 0, rangeRight = INT_MAX;int size = nums.size();vector<int> next(size);auto cmp = [&](const int& u, const int& v) {  //定义比较规则return nums[u][next[u]] > nums[v][next[v]];};priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);int minValue = 0, maxValue = INT_MIN;for (int i = 0; i < size; ++i) {pq.emplace(i);  //加的是nums数组下标,后面要根据下标计算maxValue = max(maxValue, nums[i][0]);}while (true) {int row = pq.top();pq.pop();minValue = nums[row][next[row]];if (maxValue - minValue < rangeRight - rangeLeft) {  //更新区间rangeLeft = minValue;rangeRight = maxValue;}if (next[row] == nums[row].size() - 1) {break;}++next[row];maxValue = max(maxValue, nums[row][next[row]]);pq.emplace(row);}return {rangeLeft, rangeRight};

时间复杂度:O(nklogk),其中 n 是所有列表的平均长度,k 是列表数量。

会议室||

问题描述

给你一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,返回 所需会议室的最小数量

原题链接

思路分析

在同一会议室的所有会议需要满足任意两个会议的时间区间不交叉,按开始时间升序对intervals,排序,然后枚举每个会议,将当前枚举到的会议放在结束时间最早的会议室,如果该会议室的结束时间还晚于当前会议的开始时间,则必须要新开一个会议室了,否则将当前会议加入该会议室并更新结束时间。枚举结束后统计的会议室数量就是答案。具体实现上用小顶堆维护结束时间最早的会议室

代码

int minMeetingRooms(vector<vector<int>>& intervals) {if (intervals.empty()) return 0;sort(intervals.begin(), intervals.end());priority_queue<int, vector<int>, greater<int>> mqueue;mqueue.emplace(intervals[0][1]);for (int i = 1; i < intervals.size(); i++){ if (mqueue.top() <= intervals[i][0]) mqueue.pop();mqueue.emplace(intervals[i][1]);}return mqueue.size();
}

舞狮

问题描述

一支舞狮队,每位队员的技能值严格大于前面所有队员的数量,那么这支舞狮队称为合法的舞狮队,例如[1,2,4],[2,3,5]时合法的舞狮队。

给定n个舞狮队员,第 i 个舞狮队员的技能值为 ai ,求最少能组成多少支合法的舞狮队,输出一个整数代表所求。

原题链接

思路分析

首先将所有队员,按技能值大小升序排列。从左往右(技能值越来越大)枚举每个成员,将他安排在队员最少的那个舞狮队中,如果其技能值还小于该舞狮队队员数量,则必须另起一队,具体实现上用小顶堆维护队员最少的舞狮队队员数量,并实时更新。

代码

#include <bits/stdc++.h>
using namespace std;
int main()
{// 请在此输入您的代码int n; cin>>n;vector<int>d(n);for(int i=0;i<n;i++){cin>>d[i];}sort(d.begin(),d.end());priority_queue<int,vector<int>,greater<int>>q;for(int i=0;i<n;i++){if(!q.empty()&&q.top()<d[i]){int t=q.top()+1;q.pop();q.push(t);}else{q.push(1);}}cout<<q.size();return 0;
}

查找和最小的k对数字

问题描述

给定两个以 非递减顺序排列 的整数数组 nums1nums2 , 以及一个整数 k

定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2

请找到和最小的 k 个数对 (u1,v1), (u2,v2)(uk,vk)

原题链接

思路分析

本题要求找到最小的 k 个数对,最直接的办法是可以将所有的数对求出来,然后利用排序或者 Top K 解法求出最小的 k 个数对即可。实际求解时可以不用求出所有的数对,只需从所有符合待选的数对中选出最小的即可。

我们可以利用堆的特性求出待选范围中最小数对的索引为 (ai,bi),同时将新的待选的数对 (ai+1,bi), (ai,bi+1)加入到堆中,直到我们选出 k 个数对即可。

重复问题:如果我们每次都将已选的数对 (ai,bi) 的待选索引 (ai+1,bi),(ai,bi+1)加入到堆中,这可能出现同一数对重复入堆的问题,例如当(1,0)出堆时,会把(1,1)入堆;当(0,1)出堆时,也会把 (1,1) 入堆。

为了避免重复入堆,可以用哈希表标记,但这增加了复杂性,有没有更好的办法?

解决:其实只要保证 (i−1,j)(i,j−1) 中的其中一个会将 (i,j) 入堆,而另一个什么也不做,就不会出现重复了!

不妨规定(i,j−1)出堆时,将 (i,j) 入堆;而 (i−1,j) 出堆时只计入答案,不执行入堆操作。

我们可以将 nums1的前 k 个索引nums2的0索引组成的数对(0,0),(1,0),…,(k−1,0)加入到队列中,每次从队列中取出元素 (x,y) 时,我们只需要将 nums2的索引增加即可,这样避免了重复加入元素的问题。

换个角度理解

nums1=[1,7,11],nums2=[2,4,6]。我们把每个数对的和算出来,可以得到一个矩阵 M:
35791113131517\begin{matrix} 3&5&7\\ 9&11&13\\ 13&15&17 \end{matrix} 39135111571317
由于 nums2是递增的,所以矩阵每一行都是递增的。问题相当于:

  • 合并 n 个升序列表,找前 k 小元素。(其中 n 是 nums1的长度)

这样理解后,这题和21.合并k个升序列表的做法一致。

代码

vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {auto cmp = [&nums1, &nums2](const pair<int, int> & a, const pair<int, int> & b) {return nums1[a.first] + nums2[a.second] > nums1[b.first] + nums2[b.second];};int m = nums1.size();int n = nums2.size();vector<vector<int>> ans;   priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp);for (int i = 0; i < min(k, m); i++) {pq.emplace(i, 0);}while (k-- > 0 && !pq.empty()) {auto [x, y] = pq.top(); pq.pop();ans.emplace_back(initializer_list<int>{nums1[x], nums2[y]});if (y + 1 < n) {pq.emplace(x, y + 1);}}return ans;
}
作者:灵茶山艾府
http://www.dtcms.com/a/511212.html

相关文章:

  • 华为OD-23届考研-测试面经
  • 阿里云网站建设步骤wordpress防止频繁搜索
  • 西宁网站建设哪家公司好东莞seo网站推广
  • 2025年AI IDE的深度评测与推荐:从单一功能效率转向生态壁垒
  • OSS存储的视频,安卓和PC端浏览器打开正常,苹果端打开不播放,什么原因?
  • Spark的shuffle类型与对比
  • 【 论文精读】VIDM:基于扩散模型的视频生成新范式
  • CentOS 7 安装指定内核版本与切换内核版本
  • Spring MVC 拦截器interceptor
  • 如何在 CentOS、Ubuntu 和 Debian 云服务器上安装 Python 3
  • 《金融电子化》:构建金融韧性运行安全体系:从灾备管理到主动防御新范式​​
  • spark组件-spark core(批处理)
  • 进行网站建设视频教程装修网站cms
  • 解决Kali虚拟机中VMnet1(仅主机模式)网卡无法获取IP地址的问题
  • Linux驱动开发笔记(十一)——阻塞和非阻塞IO
  • Docker----快速入门
  • 深度学习8-卷积神经网络-CNN概述-卷积层-池化层-深度卷积神经网络-案例:服装分类
  • 厦门做外贸网站国内十大咨询公司排名
  • 架构设计过去十年与未来十年
  • Nginx 日志轮转
  • 《Linux运维总结:基于ARM64+X86_64架构CPU使用docker-compose一键离线部署mongodb 7.0.22容器版副本集群》
  • 《Linux运维总结:基于ARM64+X86_64架构CPU使用docker-compose一键离线部署mongodb 7.0.22容器版分片集群》
  • MongoDB基础与Mongoose ODM
  • 做定制网站价格教做flash的网站
  • 【流量控制】算不对 GBN 窗口?分不清 SR 重传?滑动窗口 + 3 大协议一篇吃透
  • 临时插入的紧急任务如何影响整体进度
  • 国内net开发的网站建设网站建设费如何会计处理
  • Melos 使用指南:Flutter / Dart 多包管理工具!
  • React组件完全指南
  • TypeScript:npm的types、typings、@type的区别