在算法面试中,数组与哈希表 类题目是绝对的高频考点。今天我们通过两道经典题目 ------ 215. 数组中的第K个最大元素 和 347. 前 K 个高频元素,来深入剖析其解题思路、算法选型与时间空间复杂度的权衡。
一、215. 数组中的第K个最大元素
📌 题目描述
给定整数数组 nums和整数 k,返回数组中第 k个最大的元素。(注意:是第 k个最大,不是第 k个不同元素)
示例:
-
输入:
[3,2,1,5,6,4],k = 2→ 输出:5 -
输入:
[3,2,3,1,2,4,5,5,6],k = 4→ 输出:4
🧠 解题思路
题目看似简单,但关键在于如何高效找到"第 K 大"元素。常见解法有三种:
✅ 方法一:最小堆(图片中代码所用)
-
思路 :维护一个大小为
k的最小堆,堆顶始终是当前堆中最小的元素(即第k大的候选)。 -
过程:
-
遍历数组,将每个元素压入堆。
-
若堆大小 >
k,弹出堆顶(丢弃当前最小的)。 -
最终堆顶即为第
k大元素。
-
-
时间复杂度:O(n log k)
-
空间复杂度:O(k)
💡 优点:实现简单,适合大数据流或部分排序场景。
⚠️ 缺点:未达到题目提示的 O(n) 最优复杂度。
🚀 方法二:快速选择(Quick Select)------ 满足 O(n) 要求
-
思路:基于快速排序的分区思想,每次将数组划分为"大于 pivot"和"小于 pivot"两部分,根据 pivot 的位置决定递归哪一侧。
-
平均时间复杂度:O(n)
-
最坏情况:O(n²)(可通过随机化 pivot 优化)
📦 方法三:排序法(暴力解)
- 直接排序后取
nums[n-k],时间复杂度 O(n log n),不推荐用于面试。
💻 图片中代码解析(C++)
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> minHeap;
for(int num:nums) {
minHeap.push(num);
if(minHeap.size() > k)
minHeap.pop();
}
return minHeap.top();
}
};
✅ 优点:
-
逻辑清晰,易于理解。
-
适合处理流式数据或内存受限场景。
❌ 缺点:
- 未满足题目"O(n) 时间复杂度"的要求(虽然 LeetCode 通常接受堆解法,但面试中可能被追问更优解)。
二、347. 前 K 个高频元素
📌 题目描述
给定一个整数数组 nums和整数 k,返回出现频率前 k高的元素(顺序可任意)。
示例:
-
输入:
[1,1,1,2,2,3],k = 2→ 输出:[1,2] -
输入:
[1],k = 1→ 输出:[1]
🧠 解题思路
核心是:统计频率 + 取前 K 个高频。
✅ 方法一:哈希表 + 最小堆(图片中代码所用)
-
步骤:
-
用
unordered_map统计每个元素的频率。 -
用最小堆维护前
k个高频元素(堆顶是频率最小的)。 -
遍历频率表,若堆大小 <
k,直接入堆;否则,若当前元素频率 > 堆顶频率,则替换。 -
最后堆中元素即为答案。
-
-
时间复杂度:O(n log k)
-
空间复杂度:O(n)
💡 优点:稳定、易懂,适合大多数面试场景。
⚠️ 注意:题目要求"任意顺序",所以不需要对结果排序。
🚀 方法二:桶排序(Bucket Sort)------ 更优解法
-
思路:
-
统计频率 → 得到频率最大值
maxFreq。 -
创建大小为
maxFreq + 1的桶,桶索引 = 频率,桶内存储对应元素。 -
从后往前遍历桶,取出元素直到凑够
k个。
-
-
时间复杂度:O(n)
-
空间复杂度:O(n)
💻 图片中代码解析(C++)
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> freqMap;
for(int num:nums)
freqMap[num]++;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;
for (auto& entry : freqMap) {
int num = entry.first;
int freq = entry.second;
minHeap.push({freq, num});
if (minHeap.size() > k) {
minHeap.pop(); // 保持堆大小为 k,弹出频率最小的
}
}
vector<int> result;
while (!minHeap.empty()) {
result.push_back(minHeap.top().second);
minHeap.pop();
}
return result;
}
};
✅ 亮点:
-
使用
pair<int, int>存储频率和元素,便于堆比较。 -
堆大小控制在
k,避免内存浪费。 -
最后返回结果时无需排序,符合题目"任意顺序"要求。
📊 总结对比
| 题目 | 解法 | 时间复杂度 | 空间复杂度 | 是否推荐面试 |
|---|---|---|---|---|
| 215. 第K个最大元素 | 最小堆 | O(n log k) | O(k) | ✅ 基础解法,必会 |
| 215. 第K个最大元素 | 快速选择 | O(n) | O(1) | ✅ 高阶解法,面试加分项 |
| 347. 前K个高频元素 | 哈希+最小堆 | O(n log k) | O(n) | ✅ 标准解法,必会 |
| 347. 前K个高频元素 | 桶排序 | O(n) | O(n) | ✅ 最优解法,体现算法深度 |
🎯 面试建议
-
先写堆解法:逻辑清晰,面试官容易理解,适合快速通过。
-
再提优化:如"如果追求 O(n) 时间,可以用快速选择或桶排序",体现思维深度。
-
注意边界 :如
k=1、k=n、空数组等特殊情况。 -
代码规范:变量命名清晰、注释到位、避免硬编码。
📚 延伸阅读
-
堆排序与优先队列\]([https://en.wikipedia.org/wiki/Heap_(data_structure)](https://en.wikipedia.org/wiki/Heap_%28data_structure%29 "https://en.wikipedia.org/wiki/Heap_(data_structure)"))
📌 一句话总结:
堆是解决"Top K"问题的万能钥匙,但真正的算法高手,懂得在合适场景选择更优的 O(n) 解法。
如果你正在准备算法面试,这两道题是必刷经典,建议亲手实现并对比不同解法的优劣。祝你刷题顺利,offer 拿到手软! 🚀