前 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. 基本操作

它的接口和普通的 stack 或 queue 非常相似:

操作 函数 说明 时间复杂度
入队 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) 是怎么来的)

相关推荐
aqiu11111125 分钟前
【C++算法打怪专栏】8. 字符串转换整数 (atoi)
开发语言·c++·算法
信奥卷王9 小时前
[GESP202609 六级] 分树规划
算法
夏幻灵9 小时前
LeetCode 21:合并两个有序链表——双指针与哑节点详解
算法·leetcode·链表
徐小黑ACG10 小时前
Golang 基础05 结构体struct
开发语言·算法·golang
码完就睡11 小时前
数据结构——红黑树
数据结构
码爸11 小时前
数据结构详细介绍
数据结构
Navigator_Z11 小时前
LeetCode //C - 1286. Iterator for Combination
c语言·算法·leetcode
北极有牛11 小时前
cpp学习笔记--常量指针
java·开发语言·算法
GPU实战笔记11 小时前
云端 GPU 训练的账户余额提醒:它能说明什么,不能说明什么?
深度学习·算法·成本管理·gpu云计算·云端训练