首页 > 其他分享 >代码随想录——栈与队列8-前K个高频元素

代码随想录——栈与队列8-前K个高频元素

时间:2024-10-30 11:11:03浏览次数:1  
标签:std 队列 元素 随想录 second vector pair 高频


法一、用数组排序

思路

  1. 用map保存元素和频率关系

  2. 将元素和频率的键值对pair作为vector的基本元素,以频率为准进行从大到小的排序 —— O(nlogn)

  3. 输出前K个pair的first,即数字本身

代码

class Solution {
   public:
       std::vector<int> topKFrequent(std::vector<int>& nums, int k) {
           std::unordered_map<int, int> frequencyMap;
           for (int num : nums) {
               frequencyMap[num]++;
           }
           std::vector<std::pair<int, int>> frequencyPairs;
           for (const auto& pair : frequencyMap) {
               frequencyPairs.push_back(pair);
           }
           std::sort(frequencyPairs.begin(), frequencyPairs.end(), [](const std::pair<int, int>& a, const std::pair<int, int>& b) {
               return a.second > b.second;
           });
           std::vector<int> result;
           for (int i = 0; i < k; ++i) {
               result.push_back(frequencyPairs[i].first);
           }
           return result;
       }
   };

这里用到了lambda表达式定义比较规则。在 C++ 中,可以使用函数指针、函数对象(如类或结构体的重载运算符)或 lambda 表达式来定义比较规则,实现效果是相同的,但语法和使用方式略有不同。(下面的优先队列就是用类定义比较规则)

但题目说明时间复杂度需要优于O(nlogn),而此方法使用的sort函数复杂度等于O(nlogn),因此还不够好。

法二、堆

sort对所有N个元素进行了排序,而题目只要求输出前K个高频元素,那么有没有方法可以只排序K个元素,其他元素不参与排序呢(只与最大/最小值比较,不与其他K-1个元素比较大小),借助堆可以实现。

堆的选择:大根堆 or 小根堆

采用大根堆时,最大值在堆顶,若已有K个元素且需要更新时只能弹出堆顶,这样就失去了最大值。

采用小根堆时,最小值在堆顶,更新堆时弹出的元素一定小于当前堆中所有元素,最后积累的K个元素一定是前K个最大元素。

因此采用小根堆。

优先队列

在 C++ 中,<priority_queue> 是标准模板库(STL)的一部分,用于实现优先队列。

优先队列是一种特殊的队列,它允许我们快速访问队列中具有最高(或最低)优先级的元素。

在 C++ 中,priority_queue 默认是一个最大堆,这意味着队列的顶部元素总是具有最大的值。

priority_queue 是一个容器适配器,它提供了对底层容器的堆操作。它不提供迭代器,也不支持随机访问。

引用自菜鸟教程

从这里可以看出,在C++可以使用优先队列实现堆,且在参数缺省时默认构造大根堆。实现小根堆则要更改比较规则,这里提供两种语法。

1. 静态函数

	static bool cmp(pair<int,int>& m,pair<int,int>& n){
			return m.second > n.second; //堆的实现就是n更小于是让n作为父节点——维护最小堆
	}
	priority_queue<pair<int,int>,vector<pair<int,int>>,decltype(&cmp)> q(cmp);
  1. priority_queue
    这里创建了一个优先队列 q,其元素类型为 pair<int, int>
    vector<pair<int, int>>是用来存储优先队列元素的底层容器
    decltype(&cmp) 是一个类型推导,表示 cmp 函数的类型。decltype 用于获取 cmp 的类型,以便在创建优先队列时使用。

  2. 为何return m.second > n.second表示小根堆
    STL的比较器默认实现是std::less<T>,即等价为return a<b,其在sort中体现为元素从小到大排序,但在堆中却对应着堆顶最大的大根堆。这是堆的底层实现导致的:

    priority_queue 的堆构建过程依赖于比较函数来决定哪个元素应该在堆的顶部。默认的比较是 std::less,即 a < b。那么:当 a < b 时,意味着 b 比 a 更大,所以在堆的构建过程中,b 应该排在 a 的上面,让较大的元素更靠近堆顶。因此,堆顶的元素是最大的,构成了一个大顶堆。

    因此,return m.second > n.second自然是n排在m的上面,形成小根堆

    以上内容是询问GPT所得,方便理解,具体STL源码本人还未查阅,希望各位批评指正

2. 类

    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
  1. 为什么不需要decltype
    使用类的方式时,编译器已经知道这个类的类型,因此不需要使用 decltype。

思路

  1. 用map统计并保存数字和频率关系
  2. 定义小根堆,基本元素同样是数字和频率的键值对pair
  3. 遍历map,堆中元素不足k个时直接入堆,否则判断当前pair的频次是否大于堆顶,大于则堆顶弹出,当前pair入堆;小于则不做处理
  4. 输出堆中所有pair的key值-数字

代码

class Solution {
public:
    static bool cmp(pair<int,int>& m,pair<int,int>& n){
        return m.second > n.second; //堆的实现就是n更小于是让n作为父节点——维护最小堆
    }
    vector<int> topKFrequent(vector<int>& nums, int k) {
        //统计元素出现频率
        unordered_map<int,int> occur;
        for(auto& v : nums){
            occur[v]++;
        }
        //建立小根堆——只能暂时死记了
        priority_queue<pair<int,int>,vector<pair<int,int>>,decltype(&cmp)> q(cmp);  

        for(auto& [num,count] : occur){
            if(q.size() == k){
                if(q.top().second < count){
                    q.pop();
                    q.emplace(num,count);
                }
            }else{
                q.emplace(num,count);
            }
        }
        vector<int> ans;
        while(!q.empty()){
            ans.push_back(q.top().first);
            q.pop();
        }
        return ans;
    }
};

emplace_back 和push_back

这里使用q.push(num,count)报错

push:对于像std::priority_queue这种容器适配器(底层通常是堆结构),push操作主要用于向容器中添加一个新元素。当使用push添加元素时,需要先构造好要添加的元素,然后将其复制(或移动)到容器内部的存储结构中

emplace:emplace是 C++ 11 引入的一个函数,它的主要作用是在容器中直接原位构造(in - place construction)一个新元素,而不是先构造然后再复制(或移动)。这在性能上可能会有优势,特别是当元素的构造函数比较复杂或者涉及资源分配时。

因此如要使用push则应改为q.push({num,count});

标签:std,队列,元素,随想录,second,vector,pair,高频
From: https://www.cnblogs.com/neromegumi/p/18515471

相关文章

  • 解锁Kafka等消息队列中间件的测试之道
    在这个数字化时代,分布式系统已经成为我们日常生活和工作不可或缺的一部分。而消息队列中间件,如Kafka、RabbitMQ等,更是这些系统中的关键组件。它们承担着消息传递、系统解耦、异步处理等重要职责。但你是否真正了解如何对这些中间件进行有效的测试呢?大咖公开课亮点●深入剖析Kaf......
  • 代码随想录算法训练营day30| 452. 用最少数量的箭引爆气球 435. 无重叠区间 763.
    学习资料:https://programmercarl.com/0452.用最少数量的箭引爆气球.html重叠区域问题最远位置问题452.用最少数量的箭引爆气球(重叠区域;按左边界排序;i区间的左边界与i-1区间的右边界比较来确定是否重叠;更新i的右边界,取i与i-1区域右边界的最小值)点击查看代码classSolution(ob......
  • 代码随想录一刷-day3
    T209长度最小子数组核心:滑动窗口思想,如何用一个for循环达到两个循环的效果for(intj=0;j<num.size();j++){sum+=nums[j];//外层for循环内负责将窗口结束的坐标++;while(sum>=target){window_length=j-i+1;result=min(result,window_length);sum-=nums[i++];......
  • 代码随想录day14 二叉树(2)
    文章目录day11栈与队列(2)栈与队列的总结day13二叉树(1)day14二叉树(2)day11栈与队列(2)逆波兰表达式求值https://leetcode.cn/problems/evaluate-reverse-polish-notation/逆波兰表达式,也就是后缀表达式,所谓后缀就是指运算符写在后面。平常使用的算式则是一种......
  • 【数据结构】队列
    目录3.3.1队列的定义思考题3.3.2 队列的顺序存储结构及其基本运算的实现1.队列的顺序存储结构定义2.顺序队中实现队列的基本运算(1)初始化队列InitQueue(q)构造一个空队列q(2)销毁队列DestroyQueue(q)(3)判断队列是否为空QueueEmpty(q)  (4)进队列enQueue(q,e)  ......
  • 栈和队列(一)
    目录一、栈(后入先出)1、概念和结构2、栈的实现(顺序表)主程序(test.c)头文件(stack.h)调用函数(stack.c)二、队列(先入先出)1、队列的概念及结构2、队列的实现主程序(test.c)头文件(queue.h)调用函数(queue.c)一、栈(后入先出)1、概念和结构栈是一种特殊的线性表,只允许在固定......
  • 队列与树 数据结构复习笔记
    2.3队列队列(Queue),它是一种运算受限的线性表,先进先出(FIFOFirstInFirstOut)队列是一种受限的线性结构受限之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作Python标准库中的queue模块提供了多种队列实现,包括普通队列、双端队列、优先队......
  • c语言-数组队列-学习笔记
    数组队列#include<stdio.h>#include<stdlib.h>/*数组顺序队列*/typedefstructSqQueue{ intdata[10]; intfront; intrear;}SqQueue;voidInitQueue(SqQueue*Q){ Q->front=Q->rear=0;}voidEnQueue(SqQueue*Q,inta){ Q->data[Q->rear......
  • 2024前端面试训练计划-高频题-JavaScript基础篇
    具体内容结构(可作为回答思路)为:简略回答,详细回答1、JavaScript有几种数据类型?简略回答JavaScript共有八种数据类型,分别是Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。详细回答具体来说,分为两种类型:原始数据类型和引用数据类型:原始数据类型......
  • 代码随想录-栈与队列6、7
    逆波兰表达式思路用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中这里记录string类型相关操作:判断token是否是数字,不可像char类型用string重载的>=,<=,前者由于用ASCII码表示,后者按字典序比较,例如1<2所以字符串比较"10"<"2"。所以直接......