前 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) 是怎么来的)

相关推荐
YH55269845 小时前
GPT‑5.6 Sol 原本支持 1M 上下文,Codex 现已放开此前限制,如何看待这次调整?
java·jvm·人工智能·gpt·算法·chatgpt
遥感知识服务7 小时前
从局部阈值、双极化到暗地表剔除:NASA OPERA DSWx-S1全球动态水体算法拆解
大数据·人工智能·深度学习·神经网络·算法·机器学习
晚风醉蝶7 小时前
1-13-TimSort
算法
ZJU_统一阿萨姆7 小时前
【算子开发】算子融合与Softmax_LayerNorm实现
人工智能·算法·语言模型·硬件架构
叩码以求索9 小时前
浅谈:前序遍历反转法求解N叉树的后序遍历
算法
北风toto10 小时前
中缀、前缀、后缀表达式
算法·软件设计师
听取WA声一片(无恶意)10 小时前
CSP-J/CSP-S 深度优先搜索(DFS)完全讲义
c++·算法·深度优先
码流怪侠11 小时前
2026年8月GitHub热榜深度拆解:Agent Skills席卷开源圈,一个“技能包“收割5万星
算法·程序员·github
hold?fish:palm11 小时前
30 两两交换链表中的节点
数据结构·算法·链表
Nil20811 小时前
leetcode 98验证二叉搜索树
算法·leetcode·职场和发展