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

相关文章

hdu1180(广搜+优先队列)

此题要求最少到达目标点T的最短时间,所以我选择了广度优先搜索,并且要用到优先队列。 另外此题注意点较多,比如说可以在某个点停留,我wa了好多两次,就是因为忽略了这一点,然后参考了大神的思想,然后经过反复修改才AC的 这是我的代码 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

poj 3190 优先队列+贪心

题意: 有n头牛,分别给他们挤奶的时间。 然后每头牛挤奶的时候都要在一个stall里面,并且每个stall每次只能占用一头牛。 问最少需要多少个stall,并输出每头牛所在的stall。 e.g 样例: INPUT: 51 102 43 65 84 7 OUTPUT: 412324 HINT: Explanation of the s

poj 2431 poj 3253 优先队列的运用

poj 2431: 题意: 一条路起点为0, 终点为l。 卡车初始时在0点,并且有p升油,假设油箱无限大。 给n个加油站,每个加油站距离终点 l 距离为 x[i],可以加的油量为fuel[i]。 问最少加几次油可以到达终点,若不能到达,输出-1。 解析: 《挑战程序设计竞赛》: “在卡车开往终点的途中,只有在加油站才可以加油。但是,如果认为“在到达加油站i时,就获得了一

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝