C++ 优先级队列(大小根堆)OJ

2024-03-16 19:36

本文主要是介绍C++ 优先级队列(大小根堆)OJ,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1、 1046. 最后一块石头的重量

2、 703. 数据流中的第 K 大元素

        为什么小根堆可以解决TopK问题? 

3、 692. 前K个高频单词

4、 295. 数据流的中位数


1、 1046. 最后一块石头的重量

 思路:根据示例发现可以用大根堆(降序)模拟这个过程。

class Solution {
public:int lastStoneWeight(vector<int>& stones) {priority_queue<int> heap;for (auto s : stones)heap.push(s);while (heap.size() > 1) {int a = heap.top();heap.pop();int b = heap.top();heap.pop();if (a > b)heap.push(a - b);}return heap.size() ? heap.top() : 0;}
};

2、 703. 数据流中的第 K 大元素

 思路:TopK问题使用小根堆堆解决,priority_queue(默认大根堆)的第三个参数为greater<类型>即为小根堆。

class KthLargest {
public:int _k;priority_queue<int, vector<int>, greater<int>> heap;KthLargest(int k, vector<int>& nums) {_k = k;for (auto n : nums) {heap.push(n);if (heap.size() > _k)heap.pop();}}int add(int val) {heap.push(val);if (heap.size() > _k)heap.pop();return heap.top();}
};/*** Your KthLargest object will be instantiated and called as such:* KthLargest* obj = new KthLargest(k, nums);* int param_1 = obj->add(val);*/

为什么小根堆可以解决TopK问题? 

对于设计一个找到数据流中第k大元素的类的问题,我们应该使用小根堆(Min Heap)来实现。下面解释为什么使用小根堆以及如何使用它:

  1. 为什么使用小根堆:

    • 小根堆能够保证堆顶元素是堆中最小的元素。在维护数据流中的第k大元素时,我们希望能够快速访问到第k大的元素,而不是最小的元素。通过维护一个大小为k的小根堆,堆中的元素就是数据流中最大的k个元素,而堆顶元素(最小元素)就是这k个元素中第k大的元素。
    • 当新的元素加入时,如果它大于堆顶元素,我们就将它加入堆中,并移除堆顶元素,这样堆的大小仍然保持为k。这样做可以确保堆中始终是数据流中最大的k个元素,而堆顶元素就是这些元素中最小的,即第k大的元素。
  2. 如何使用小根堆:

    • 初始化时,将数组nums中的元素加入小根堆中,如果元素数量超过k,则移除堆顶元素,以保证堆的大小为k。
    • 对于add方法,每次加入一个新元素时,先将其加入到小根堆中。如果加入后堆的大小超过k,则移除堆顶元素。然后返回堆顶元素,即为当前数据流中第k大的元素。

通过使用小根堆,我们可以高效地解决数据流中的第k大元素问题,同时保证时间复杂度和空间复杂度都在合理的范围内。

3、 692. 前K个高频单词

 思路:

  • 频次统计:首先,使用哈希表记录每个单词出现的频次。
  • 优先队列排序:利用优先队列(或小根堆)根据单词的频次和字典序排序,从而找出频次最高的前 k 个单词。

实现步骤

  1. 处理单词列表

    • 利用哈希表统计每个单词出现的次数,确保单词不会重复计数且记录了它们的频次。
  2. 使用小根堆选择前 k 大元素

    • 根据问题要求,设计比较器(cmp):
      • 频次不同:频次较少的优先(小根堆性质)。
      • 频次相同:字典序较小的优先(大根堆性质)。
    • 使用优先队列(小根堆)存储单词及其频次,保证堆的大小不超过 k
    • 将每个单词和它的频次插入到堆中,如果堆的大小超过了 k,就移除堆顶元素。
  3. 获取结果

    • 最终,堆中剩余的就是频次最高的前 k 个单词。反向遍历堆,将元素按正确的顺序存入结果列表中。
class Solution {
public:typedef pair<string,int> PSI;struct cmp{bool operator()(const PSI& a,const PSI& b){if(a.second==b.second){return a.first<b.first;}return a.second>b.second;}};vector<string> topKFrequent(vector<string>& words, int k) {unordered_map<string,int> hash;for(auto& s:words)hash[s]++;priority_queue<PSI,vector<PSI>,cmp> heap;for(auto& psi:hash){heap.push(psi);if(heap.size()>k)heap.pop();}vector<string> ret(k);for(int i=k-1;i>=0;i--){ret[i]=heap.top().first;heap.pop();}return ret;}
};

4、 295. 数据流的中位数

实现一个动态中位数查找器

 动态数据流中位数查找是一种常见问题,可以通过聪明地运用数据结构来解决。以下是如何通过维护两个堆:一个大根堆和一个小根堆—来实现一个动态中位数查找器的步骤。

解决方法:利用两个堆

算法思路

本解法是一个关于堆数据结构的经典应用。通过将数据流平分或近似平分为两个部分:一个较小部分和一个较大部分—可以高效解决问题:

  • 较小的部分在大根堆(left)中。

  • 较大的部分在小根堆(right)中。

  • 如此设置允许我们在常数时间内获得中位数。

动态添加数据

我们需要保证left比right大1或者left等于right,这样才能算出中位数,当有新数据加入时,进行以下步骤以保持两个堆的平衡:

  1. 两个堆的大小相同 (left.size() == right.size()):

    • 若堆为空,直接将 num 放入 left 中。

    • 若 num <= left.top(),将 x 放入 left

    • 否则,先将 num 放入 right,再将 right 的堆顶元素移到 left 中。

  2. 两个堆的大小不相同 (left.size() > right.size()+1):

    • 若 num 小于或等于 left.top(),再将 left 的堆顶元素移到 right 中。

    • 否则,将 num 放入 right

查找中位数

  • 若两个堆的大小相同,中位数是两个堆顶元素的平均值。

  • 否则,中位数是 left 堆的顶元素。
class MedianFinder
{priority_queue<int> left; // 大根堆priority_queue<int, vector<int>, greater<int>> right; // 小根堆public:MedianFinder() {}void addNum(int num){// 分类讨论即可if(left.size() == right.size()) // 左右两个堆的元素个数相同{if(left.empty() || num <= left.top()) // 放 left 里面{left.push(num);}else{right.push(num);left.push(right.top());right.pop();}}else{if(num <= left.top()){left.push(num);right.push(left.top());left.pop();}else{right.push(num);}}}double findMedian(){if(left.size() == right.size()) return (left.top() + right.top()) / 2.0;else return left.top();}
};

这篇关于C++ 优先级队列(大小根堆)OJ的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/816501

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

RabbitMQ 延时队列插件安装与使用示例详解(基于 Delayed Message Plugin)

《RabbitMQ延时队列插件安装与使用示例详解(基于DelayedMessagePlugin)》本文详解RabbitMQ通过安装rabbitmq_delayed_message_exchan... 目录 一、什么是 RabbitMQ 延时队列? 二、安装前准备✅ RabbitMQ 环境要求 三、安装延时队

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

SQL Server 查询数据库及数据文件大小的方法

《SQLServer查询数据库及数据文件大小的方法》文章介绍了查询数据库大小的SQL方法及存储过程实现,涵盖当前数据库、所有数据库的总大小及文件明细,本文结合实例代码给大家介绍的非常详细,感兴趣的... 目录1. 直接使用SQL1.1 查询当前数据库大小1.2 查询所有数据库的大小1.3 查询每个数据库的详

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用