前 K 个高频元素

cpp 复制代码
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> mp; // key: 数字, value: 出现频率
        // 默认是大顶堆,存储 pair<频率, 数字>
        // pair 比较时先看第一个元素(频率),再看第二个元素(数字)
        priority_queue<pair<int, int>> pq; 
        vector<int> res;

        // 1. 统计频率:遍历一遍数组 O(N)
        for(int num : nums) {
            mp[num]++;
        }

        // 2. 将频率和数字压入大顶堆:O(M log M),M 是去重后的数字个数
        for(auto it = mp.begin(); it != mp.end(); it++) {
            // 注意:这里把 it->second(频率)放在前面,方便堆按频率排序
            pq.push(pair<int, int>(it->second, it->first));
        }

        // 3. 弹出前 k 个频率最高的数字:O(K log M)
        while(k--) {
            res.push_back(pq.top().second); // 取出数字部分
            pq.pop();
        }

        return res;
    }
};

2. 运行步骤模拟

假设输入:nums = [1, 1, 1, 2, 2, 3], k = 2

第一步:哈希表计数

  • mp = {1: 3, 2: 2, 3: 1} (数字 1 出现了 3 次,依此类推)

第二步:压入大顶堆 堆会根据 pair 的第一个元素(频率)自动调整,此时堆的内容(逻辑结构)如下:

  1. (3, 1) <- 堆顶(频率最高)

  2. (2, 2)

  3. (1, 3)

第三步:取出结果

  1. 第一次 pop:取回 1(频率为 3),res = [1]

  2. 第二次 pop:取回 2(频率为 2),res = [1, 2]

  3. k 减到 0,循环结束。

最终结果[1, 2]

在 C++ STL 中,priority_queue(优先队列)是一个非常实用的容器适配器。

简单来说,它就像是一个**"自带排队规则"的队列**。普通的队列(queue)是先进先出(FIFO),而优先队列是**"最优先的先出"**。


1. 核心特性:谁强谁先出

priority_queue 中,元素并不是按照进入的先后顺序排列的,而是按照优先级排列的。

  • 默认行为(大顶堆):默认情况下,优先级最高的是"数值最大"的元素。即使你是最后一个进去的,只要你最大,你也会排在最前面。

  • 底层实现 :它的底层通常是用 二叉堆 (Binary Heap) 实现的。这保证了插入元素和删除堆顶元素的时间复杂度都是 O(\\log N)


2. 基本操作

它的接口和普通的 stackqueue 非常相似:

操作 函数 说明 时间复杂度
入队 pq.push(val) 将元素插入队列并自动调整位置 O(\\log N)
出队 pq.pop() 删除优先级最高的元素(堆顶) O(\\log N)
查顶 pq.top() 返回优先级最高的元素(不删除) O(1)
判空 pq.empty() 检查队列是否为空 O(1)

3. 三种常见的定义方式

面试中经常需要根据需求改变排序规则:

A. 默认:大顶堆(大的先出)

C++

复制代码
// 默认把最大的整数放在堆顶
priority_queue<int> pq; 
B. 小顶堆(小的先出)

如果你想找"前 K 个最小"或者让小的数字先出来:

C++

复制代码
// 需要三个参数:元素类型,底层容器,比较函数
priority_queue<int, vector<int>, greater<int>> pq; 
C. 自定义比较(处理结构体或 Pair)

就像你之前写的 pair<频率, 数字>

C++

复制代码
// 存储 pair,默认先比较 pair.first,再比较 pair.second
priority_queue<pair<int, int>> pq; 

4. 形象理解:急诊室排队

  • 普通队列 (queue):银行办业务。谁先来谁先办,即便你只是存 100 块,也要等前面取 100 万的人办完。

  • 优先队列 (priority_queue):医院急诊室。医生不看谁先来,而是看谁的伤势最重(优先级最高)。伤势最重的人(比如大顶堆里的最大值)永远第一个就诊。


5. 为什么面试爱考它?

因为它在处理 "最" 字问题时极其高效:

  • 找数组中大的 K 个数。

  • 合并 K 个有序链表(每次取 K 个节点里小的)。

  • 迪杰斯特拉(Dijkstra)最短路径算法(每次取距离短的点)。

既然你已经知道 priority_queue 是基于"堆"实现的,你想看看它是如何通过"上浮"和"下沉"操作来维持这个顺序的吗?(这能帮你更好地理解 O(\\log N) 是怎么来的)

相关推荐
汉克老师2 小时前
GESP5级C++考试语法知识(十一、递归算法(一))
c++·算法·记忆化搜索·递归算法·递归优化
历程里程碑2 小时前
Proto3 三大高级类型:Any、Oneof、Map 灵活解决复杂业务场景
java·大数据·开发语言·数据结构·elasticsearch·链表·搜索引擎
qq_148115372 小时前
C++网络编程(Boost.Asio)
开发语言·c++·算法
2301_804215412 小时前
内存映射文件高级用法
开发语言·c++·算法
爱喝白开水a2 小时前
春节后普通程序员如何“丝滑”跨行AI:不啃算法,也能拿走AI
java·人工智能·算法·spring·ai·前端框架·大模型
张辰宇-3 小时前
AcWing 5 多重背包问题 II
算法
小则又沐风a3 小时前
类和对象(C++)---上
java·c++·算法
季明洵3 小时前
动态规划及背包问题
java·数据结构·算法·动态规划·背包问题
busideyang3 小时前
函数指针类型定义笔记
c语言·笔记·stm32·单片机·算法·嵌入式