用priority_queue搞定LeetCode前K个高频元素:C++ STL实战解法与避坑指南

张开发
2026/4/17 2:19:50 15 分钟阅读

分享文章

用priority_queue搞定LeetCode前K个高频元素:C++ STL实战解法与避坑指南
用priority_queue搞定LeetCode前K个高频元素C STL实战解法与避坑指南在算法面试和日常刷题中Top K问题几乎是必考题。LeetCode 347题要求找出数组中出现频率前K高的元素这类问题考验的不仅是基础编码能力更是对数据结构和算法选择的深刻理解。本文将带你用C STL中的std::priority_queue优先队列高效解决这个问题重点剖析为什么小顶堆比大顶堆更适合此场景以及如何避免实际编码中的常见陷阱。1. 理解问题本质与数据结构选择遇到前K个高频元素问题时新手常犯的错误是直接排序整个频率表。虽然这种方法能得到正确答案但其O(n log n)的时间复杂度并不是最优解。更聪明的做法是利用堆Heap这种数据结构将时间复杂度优化到O(n log k)。为什么是堆堆是一种特殊的完全二叉树满足以下性质大顶堆每个节点的值都大于或等于其子节点的值小顶堆每个节点的值都小于或等于其子节点的值堆的核心优势在于插入/删除操作的时间复杂度为O(log n)获取堆顶元素最大值或最小值的时间复杂度为O(1)对于Top K问题我们只需要维护当前频率最高的K个元素堆的这两个特性正好契合需求。大顶堆 vs 小顶堆的选择这里有个关键决策点该用大顶堆还是小顶堆让我们用实际数据对比假设有元素频率表{A:3, B:5, C:1, D:4, E:2}要求找出前2个高频元素。大顶堆方案将所有元素插入大顶堆[B:5, D:4, A:3, E:2, C:1]取出前K个元素B:5, D:4小顶堆方案维护一个大小为K2的小顶堆插入B:5 →[B:5]插入D:4 →[D:4, B:5]堆调整遇到A:3时因为3 堆顶的4跳过同理跳过E:2和C:1最终堆中保留的就是[D:4, B:5]虽然两种方案都能得到正确答案但小顶堆的空间复杂度是O(k)而大顶堆是O(n)。当n远大于k时这是常见情况小顶堆的优势就非常明显了。提示在面试中面试官通常会追问为什么选择小顶堆而不是大顶堆这时你需要清楚解释空间复杂度的差异。2. std::priority_queue深度解析C STL中的std::priority_queue本质上是一个堆的实现默认情况下是大顶堆。理解它的模板参数对正确使用至关重要。2.1 priority_queue的模板参数priority_queue的完整声明形式为template class T, class Container std::vectorT, class Compare std::lesstypename Container::value_type class priority_queue;三个模板参数的含义T存储的元素类型Container底层容器必须是随机访问容器如vector或dequeCompare比较函数对象决定是大顶堆还是小顶堆2.2 构造大小顶堆的两种方式大顶堆构造// 默认方式使用less比较函数 priority_queueint max_heap1; // 显式指定方式 priority_queueint, vectorint, lessint max_heap2;小顶堆构造// 必须显式指定greater比较函数 priority_queueint, vectorint, greaterint min_heap;关键点在于理解less和greater的实际效果less形成大顶堆因为默认是比较堆顶是最大元素greater形成小顶堆因为比较堆顶是最小元素2.3 自定义比较函数对于复杂数据类型我们需要自定义比较函数。以本题为例我们需要根据元素的频率进行比较struct Compare { bool operator()(const pairint, int a, const pairint, int b) { return a.second b.second; // 小顶堆 } }; priority_queuepairint, int, vectorpairint, int, Compare min_heap;注意比较函数中的符号这决定了是小顶堆。如果写反了整个算法就会出错。3. 完整解题步骤与代码实现现在我们将所有知识点整合解决LeetCode 347题。3.1 算法步骤拆解统计频率遍历数组用哈希表记录每个元素的出现次数构建小顶堆维护一个大小为K的小顶堆当堆大小小于K时直接插入元素当堆大小等于K时比较当前元素频率与堆顶元素频率如果当前频率更大弹出堆顶插入当前元素否则跳过收集结果将堆中元素输出3.2 完整实现代码#include vector #include queue #include unordered_map #include algorithm using namespace std; vectorint topKFrequent(vectorint nums, int k) { // 1. 统计频率 unordered_mapint, int freq_map; for (int num : nums) { freq_map[num]; } // 2. 定义比较函数和小顶堆 auto cmp [](const pairint, int a, const pairint, int b) { return a.second b.second; }; priority_queuepairint, int, vectorpairint, int, decltype(cmp) min_heap(cmp); // 3. 维护大小为K的小顶堆 for (const auto entry : freq_map) { if (min_heap.size() k) { min_heap.push(entry); } else if (entry.second min_heap.top().second) { min_heap.pop(); min_heap.push(entry); } } // 4. 收集结果 vectorint result; while (!min_heap.empty()) { result.push_back(min_heap.top().first); min_heap.pop(); } reverse(result.begin(), result.end()); // 因为是小顶堆需要反转 return result; }3.3 复杂度分析时间复杂度O(n log k)统计频率O(n)堆操作最坏情况下每个元素都要入堆一次每次入堆O(log k)空间复杂度O(n k)哈希表存储频率O(n)堆存储元素O(k)4. 常见陷阱与调试技巧即使理解了算法原理实际编码时仍可能遇到各种问题。以下是几个常见陷阱及解决方法4.1 比较函数写反// 错误示例本意是小顶堆但比较方向写反了 auto cmp [](const pairint, int a, const pairint, int b) { return a.second b.second; // 实际上变成了大顶堆 };调试技巧插入测试数据{1:3, 2:2, 3:1}检查堆顶元素是否是频率最小的。4.2 忘记处理容器参数// 错误示例忘记指定底层容器 priority_queuepairint, int, decltype(cmp) min_heap(cmp); // 编译错误解决方法完整指定三个模板参数priority_queuepairint, int, vectorpairint, int, decltype(cmp) min_heap(cmp);4.3 结果顺序问题小顶堆输出的结果是频率从小到大排列的而题目通常要求从高到低排列。忘记反转结果是一个常见疏忽// 在收集结果后需要反转 reverse(result.begin(), result.end());4.4 处理边界条件空输入数组K值大于数组不同元素的数量K值为0或负数完善的代码应该处理这些边界情况if (nums.empty() || k 0) return {}; k min(k, static_castint(freq_map.size()));5. 扩展应用与变种问题掌握了这个解法后你可以轻松应对多种Top K问题的变种5.1 前K个低频元素只需修改比较函数将小顶堆改为大顶堆auto cmp [](const pairint, int a, const pairint, int b) { return a.second b.second; // 大顶堆 };5.2 处理流数据当数据以流的形式到达时不能一次性获取所有数据堆的解决方案依然适用class TopKTracker { private: priority_queuepairint, int, vectorpairint, int, Compare min_heap; unordered_mapint, int freq_map; int k; public: TopKTracker(int k) : k(k) {} void add(int num) { freq_map[num]; if (min_heap.size() k) { min_heap.push({num, freq_map[num]}); } else if (freq_map[num] min_heap.top().second) { min_heap.pop(); min_heap.push({num, freq_map[num]}); } } vectorint getTopK() { vectorint result; // ... 同前 ... return result; } };5.3 其他Top K问题变种问题类型数据结构选择关键调整点前K个高频单词堆 哈希表频率相同时按字典序排序数据流中的中位数双堆大小顶堆结合平衡两个堆的大小最近的K个点堆比较函数基于距离计算

更多文章