算法:优先级队列(堆)

目录

题目一:最后一块石头重量

题目二:数据流中的第 K 大元素

题目三:前 K 个高频单词

题目四:数据流的中位数


题目一:最后一块石头重量

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0

示例:

输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],
接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],
最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。

提示:

  • 1 <= stones.length <= 30
  • 1 <= stones[i] <= 1000

题意也非常好理解,就是每次都挑选两个最大的石头,如果这两块石头重量相同,就消除粉碎

如果这两块石头重量一大一小,就保留一个 (大 - 小) 的重量

因为每次都是挑选最大的数,在剩下的数中再挑选一个最大的

所以这时就可以引入大根堆的概念,先拿出堆顶元素,向下调整后再拿出堆顶元素,如果两者粉碎了,就不用管,如果两者变为一个了,就重新插入大根堆中即可

代码如下:

class Solution 
{
public:int lastStoneWeight(vector<int>& stones) {// 创建一个大根堆priority_queue<int> heap;// 将所有元素放入大根堆中for(auto it : stones) heap.push(it);while(heap.size() >= 2){int num1 = heap.top();heap.pop();int num2 = heap.top();heap.pop();if(num1 > num2) heap.push(num1-num2); }if(heap.empty()) return 0;else return heap.top();}
};

题目二:数据流中的第 K 大元素

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

  • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
  • int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

示例:

输入:
["KthLargest", "add", "add", "add", "add", "add"]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
输出:
[null, 4, 5, 5, 8, 8]解释:
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3);   // return 4
kthLargest.add(5);   // return 5
kthLargest.add(10);  // return 5
kthLargest.add(9);   // return 8
kthLargest.add(4);   // return 8

提示:

  • 1 <= k <= 104
  • 0 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • -104 <= val <= 104
  • 最多调用 add 方法 104 次
  • 题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素

这道题就是求第K大的元素,topK问题,题意就是先在构造函数中给出一个k,再给出一个初始的数组

因为求的是第 k 大的数,所以需要创建一个大小 k 的小根堆,堆顶的元素就是第 k 大的数,因为小根堆是从小到大排序的,堆顶的就是最小的

所以接下来,每次插入后都判断堆中的元素是否是 k 个,如果元素个数大于 k,那说明堆顶元素肯定就不是第 k 大的,所以此时需要 pop 掉堆顶元素,当堆剩余 k 个元素时,就说明此时堆顶元素是最小的,return即可

由于 k 是在构造函数中给出的,所以为了 add 函数能够得到 k,需要创建成员时,除了优先级队列,还需要多创建一个变量存储 k 的大小

代码如下:

class KthLargest 
{// 创建一个大小为 k 的堆(小根堆)priority_queue<int, vector<int>, greater<int>> heap;int _k;
public:KthLargest(int k, vector<int>& nums) {// 将 k 的值赋值给 _k,为了add函数能够得到 k 的值_k = k;for(auto it : nums) {// 每次先插入,再判断堆中的个数是否需要popheap.push(it);if(heap.size() > _k) heap.pop();}}int add(int val) {// 同样先插入,再判断个数是否需要popheap.push(val);if(heap.size() > _k) heap.pop();return heap.top();}
};

题目三:前 K 个高频单词

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。

示例 1:

输入: words = ["i", "love", "leetcode", "i", "love", "coding"], k = 2
输出: ["i", "love"]
解析: "i" 和 "love" 为出现次数最多的两个单词,均为2次。注意,按字母顺序 "i" 在 "love" 之前。

示例 2:

输入: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4
输出: ["the", "is", "sunny", "day"]
解析: "the", "is", "sunny" 和 "day" 是出现次数最多的四个单词,出现次数依次为 4, 3, 2 和 1 次。

注意:

  • 1 <= words.length <= 500
  • 1 <= words[i] <= 10
  • words[i] 由小写英文字母组成。
  • k 的取值范围是 [1, 不同 words[i] 的数量]

前k个高频单词,也就是topk问题

解法一:利用堆解决topK问题

分为下面四步解决问题:

第一步:需要搞清楚出现的次数,也就是使用哈希表统计每一个单词出现的次数

第二步:创建一个大小为 k 的堆
按频次:小根堆
按字典序 :大根堆

第三步:让元素依次进堆,判断是否超过 K 个

第四步:提取结果

代码如下:

class Solution 
{// 重命名,方便书写typedef pair<string, int> PSI;
public:struct cmp{bool operator()(const PSI& a, const PSI& b){// 频次相同,字典序按照大根堆的方式排序if(a.second == b.second)return a.first < b.first;elsereturn a.second > b.second;}};vector<string> topKFrequent(vector<string>& words, int k) {unordered_map<string, int> hash;// 统计出现的次数for(auto& it : words) hash[it]++;// 创建一个大小为 k 的堆,让元素依次进堆priority_queue<PSI, vector<PSI>, cmp> heap;for(auto& it : hash){heap.push(it);if(heap.size() > k) heap.pop();}// 提取结果vector<string> ret;while(!heap.empty()) {ret.push_back(heap.top().first);heap.pop();}// 小根堆,所以插入ret后是频次从小到大的,所以需要逆序reverse(ret.begin(), ret.end());return ret;}
};

解法二:巧妙运用multimap冗余特性与map排序

这里的解法二,巧就巧在multimap允许冗余的特性,且频次相同时不需要任何处理字典序升序的问题,理由如下:

我们可以 使用map 统计单词出现的次数后,之后设置multimap<int ,string>,将刚刚哈希表统计的数据的 first 和 second 反过来存入multimap中,此时将multimap设置为根据 int类型 值的大小 降序排序,就可以实现频次由高到低的要求

最巧妙的就是下面所说的频次相同时,为什么不需要处理:

最开始使用 map 统计单词出现的个数时,string 默认是按照字典序升序的方式排列的,所以依次将 map 中的元素插入 multimap 时,如果有频次相同的 string,先插入 multimap 的一定是字典序较小的那一个

所以频次相同时,插入 multimap 中的次序,本身就是按照字典序升序的方式排序的,所以不需要额外处理

代码如下:

class Solution 
{
public:vector<string> topKFrequent(vector<string>& words, int k) {// 统计各个单词出现的次数,一定是map而不是unordered_map,因为string需要排序map<string, int> hash;for(auto& it : words) hash[it]++;// 利用multimap存储<int, string>,按照频次排序multimap<int, string, greater<int>> mp;for(auto& it : hash){mp.insert(make_pair(it.second, it.first));}// 提取结果vector<string> ret;auto it = mp.begin();while(k--){ret.push_back((*it).second);it++;}return ret;}
};

题目四:数据流的中位数

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

  • 例如 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 以内的答案将被接受。

示例 1:

输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1);    // arr = [1]
medianFinder.addNum(2);    // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3);    // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0

提示:

  • -105 <= num <= 105
  • 在调用 findMedian 之前,数据结构中至少有一个元素
  • 最多 5 * 104 次调用 addNum 和 findMedian

解法一:每次add都sort

这道题首先最容易想到的就是每次插入一个数据,都sort,再找中位数,但是这种操作每次add函数的时间复杂度都是 O(N * logN)的

所以如果插入的数据比较多,一定会超时

解法二:插入排序思想

这里的插入排序,就不需要每次add都执行sort,因为每次插入前,此时数组的数字是有序的,所以只需要从后向前比较,直到遇到比插入数小的再插入,所以插入排序的思想的时间复杂度是O(N)的

这个解法与上面的直接sort排序一样, 如果add插入的数据非常多,也会导致超时

解法三:大小堆维护数据流的中位数

这个解法是需要知道的,具体如下:

大小堆维护中位数,也就是将前半段数据放入大根堆中,后半段数据放入小根堆

如果偶数,那么大根堆的堆顶元素和小根堆的堆顶元素,刚好就是最中间的两个中位数,因为排好序后,大根堆的堆顶元素就是左边最大的,而小根堆的堆顶元素就是右边最小的

而如果是奇数,我们可以规定,左边的元素个数是大于右边元素个数的,所以如果是奇数,就去左边大根堆的堆顶元素即可

此时相比于上面的两种解法,优化了非常多,这里执行 add函数 时,相当于堆的插入,所以时间复杂度就是O(logN)

上述就是大小堆维护中位数的规则,下面说说这种解法需要注意的细节:

假设左边堆叫做left,左边堆的元素个数是m,左边堆的堆顶元素是x
      右边堆叫做right,右边堆的元素个数是n,右边堆的堆顶元素是y

因为上面说了,m == n 或 m == n + 1,那么此时如果有一个数num想进入堆中,就有可能会破坏这个规定,例如 m == n 时,一个数num进入right,此时就会破坏掉 m 和 n 的规定,所以需要处理这个问题,当插入时分类讨论即可:

①m == n (左右元素相等) 时:
num <= x 或 m == 0,此时需要进入堆 left 中, 这时没问题满足规定
num > x,此时需要进入right,进入后right会比left多一个元素,此时需要将 y 放入left中,因为y 是right中最小的,放入left中,依旧满足left都是小的,right都是大的,并且也满足m和n的数量规定

②m == n + 1 (左边比右边多一个) 时:
num <= x 或 m == 0,此时需要进入堆 left 中,这时left会比right多两个元素,不符合规定,这时将x 放入right中即可,因为 x 时left最大的,进入right依旧满足,left都是小的,right都是大的,并且也满足规定
num > x,此时需要进入right,进入后left和right数量相等,并且也满足m和n的数量规定

代码如下:

class MedianFinder 
{// left大根堆,right小根堆priority_queue<int> left;priority_queue<int, vector<int>, greater<int>> right;
public:MedianFinder() {}void addNum(int num) {// 分类讨论,左右两个堆的元素个数是否相等   if(left.size() == right.size()){// 放left,不需要调整if(left.empty() || num <= left.top())left.push(num);// 放right,需要调整else{right.push(num);left.push(right.top());right.pop();}}else{// 放left,需要调整if(left.empty() || num <= left.top()){left.push(num);right.push(left.top());left.pop();}// 放right,不需要调整else if(num > left.top())right.push(num);}}double findMedian() {// 左右堆加起来元素个数是偶数if(left.size() == right.size()){int x = left.top();int y = right.top();return (x + y) / 2.0;}// 奇数else{return left.top();}}
};

优先级队列相关习题到此结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/48410.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot+Vue的网吧管理系统(带1w+文档)

基于SpringBootVue的网吧管理系统(带1w文档) 基于SpringBootVue的网吧管理系统(带1w文档) 网吧管理系统&#xff0c;为了随时随地查看网吧管理信息提供了便捷的方法&#xff0c;更重要的是大大的简化了管理员管理网吧的方式方法&#xff0c;更提供了其他想要了解网吧管理信息及…

Unity如何使摄像机视锥体外的物体不被剔除

在默认情况下&#xff0c;Unity在渲染场景时会剔除掉摄像机视椎体以外的所有物体&#xff0c;这就会导致某些特殊情况下&#xff0c;我们希望显示在场景中的物体反而被剔除了&#xff0c;比如用Shader制作顶点动画时&#xff0c;如果物体本身的位置在摄像机以外&#xff0c;而顶…

【云原生】Prometheus 服务自动发现使用详解

目录 一、前言 二、Prometheus常规服务监控使用现状​​​​​​​ 2.1 Prometheus监控架构图 2.2 Prometheus服务自动发现的解决方案 三、Prometheus服务自动发现介绍 3.1 什么是Prometheus服务自动发现 3.2 Prometheus自动服务发现策略 3.3 Prometheus自动服务发现应用…

SQL39道常见题型

SQL1 查询所有列 现在运营想要查看用户信息表中所有的数据&#xff0c;请你取出相应结果。 select * from user_profile 结果&#xff1a; SQL2 查询多列 还是上面那个输入&#xff0c;题目换成&#xff1a;现在运营同学想要用户的设备id对应的性别、年龄和学校的数据&#…

Springboot同时支持http和https访问

springboot默认是http的 一、支持https访问 需要生成证书&#xff0c;并配置到项目中。 1、证书 如果公司提供&#xff0c;则直接使用公司提供的证书&#xff1b; 如果公司没有提供&#xff0c;也可自己使用Java自带的命令keytool来生成&#xff1a; &#xff08;1&#x…

Flink History Server配置

目录 问题复现 History Server配置 HADOOP_CLASSPATH配置 History Server配置 问题修复 启动flink集群 启动Histroty Server 问题复现 在bigdata111上执行如下命令开启socket&#xff1a; nc -lk 9999 如图&#xff1a; 在bigdata111上执行如下命令运行flink应用程序 …

windows下玩转DockerDesktop--学习笔记

视频链接 Window下玩转Docker Desktop_哔哩哔哩_bilibili 安装ubuntu 安装wsl 网站 安装 WSL | Microsoft Learn winr 输入powershell&#xff0c;输入 wsl --install 通过修改Hosts解决国内Github经常抽风访问不到 网址 maxiaof/github-hosts: 通过修改Hosts解决国内G…

手动构建线性回归(PyTorch)

import torch from sklearn.datasets import make_regression import matplotlib.pyplot as plt import random #1.构建数据 #构建数据集 def create_dataset():x,y,coefmake_regression(n_samples100,n_features1,random_state0,noise10,coefTrue,bias14.5)#将构建数据转换为张…

录入学生信息

定义学生类Student&#xff0c;包含姓名&#xff0c;年龄&#xff0c;性别&#xff0c;分数四个属性&#xff0c;提供一个用于学员信息输出的方法info(self)。编写测试代码&#xff0c;使用循环录入5位学生的信息&#xff0c;由于录入的学生信息中间使用“#”进行分隔&#xff…

Spark学习之SparkSQL

SparkSQL 1、SparkSql初识案例 :WordCount spark sql处理数据的步骤1、读取数据源2、将读取到的DF注册成一个临时视图3、使用sparkSession的sql函数&#xff0c;编写sql语句操作临时视图&#xff0c;返回的依旧是一个DataFrame4、将结果写出到hdfs上 import org.apache.spark.…

C++基础语法:STL之容器(6)--序列容器中的forward_list

前言 "打牢基础,万事不愁" .C的基础语法的学习 引入 序列容器的学习.以<C Prime Plus> 6th Edition(以下称"本书")内容理解 本书中容器内容不多只有几页.最好是有数据结构方面的知识积累,如果没有在学的同时补上 上一篇C基础语法:链表和数据结…

Linux系统命令:监控 CPU 性能的工具mpstat详解

目录 一、概述 二、语法和使用 1、基本语法 2、常用选项 三、安装 mpstat 1、Debian&#xff08;如 Ubuntu&#xff09;的系统安装 2、CentOS 或 Fedora系统的安装 &#xff08;1&#xff09;安装指令 &#xff08;2&#xff09;安装操作 3、使用 四、示例 1. 查看…

【人工智能】Python实现文本转换为语音:使用gTTS库实现

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、准备工作三、使用gTTS实现文本转换为语音详细步骤 四、人工智能与TTS技术五、总结 一、引言 文本转换为语音&#xff08;Text-to-Speech&#xff0c;简称TTS&#xff09;技术是人工智能的重要组成部分&#xf…

Unity UGUI 之 Canvas画布

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 1.UGUI是什么 首先官方手册导向了这两个连接&#xff0c;里面是ugui的基本教程 帆布 |U…

【C语言】 链表实现学生管理系统(堆区开辟空间)

总体思路都能写出来&#xff0c;问题是感觉稍微比之前的麻烦一些&#xff0c;在刚开始创建结构体的时候&#xff0c;并没有去按照链表的思路去写&#xff0c;导致写成了顺序表&#xff0c;后面就一直纠结空间怎么开辟。 链表是由一个头节点和其它申请出来的小节点连起来的&…

统计一个页面用到的html,css,js

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>统计html</title><style>* {margin: …

Web前端知识视频教程分享(四) jQuery

资料下载地址: https://545c.com/f/45573183-1334618723-883dfe?p7526 (访问密码: 7526)

【有效验证】解决SQLyog连接MYSQL的错误 1251 - Client does not support

目录 一、原因分析&#xff1a; 二、进入到mysql 三、查看当前加密方式 四、更改加密方式 五、查看是否成功 前言&#xff1a;使用一个开源软件使用sqlyog、navcat都报1251错误&#xff0c;网上都是提示升级客户端&#xff0c;还有一种就是修改mysql配置。本文就是修改配置…

虚拟机OP的LAN网口设置

问题&#xff1a;unraid通过虚拟机安装OP&#xff0c;然而一个网口连接路由器&#xff0c;总是无法为其他设备提供DHCP&#xff0c;导致无法使用。 一、虚拟机OP配置 二、OP内部配置 对于Lan网口&#xff0c;启用强制&#xff0c;这样可以防止OP被网口接的路由器产生冲突 三、…

认识接口测试

接口测试为什么重要&#xff1f; 我相信你一定听说过这样一句话&#xff1a;“测试要尽早介入&#xff0c;测试进行得越早&#xff0c;软件开发的成本就越低&#xff0c;就越能更好地保证软件质量。” 但是如何尽早地进入测试&#xff0c;作为软件测试的你&#xff0c;是不是…