给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
示例 3:
输入: nums = [1,2,1,2,1,2,3,1,3,2], k = 2
输出: [1,2]
提示:
1 <= nums.length <= 105-104 <= nums[i] <= 104k的取值范围是[1, 数组中不相同的元素的个数]- 题目数据保证答案唯一,换句话说,数组中前
k个高频元素的集合是唯一的
进阶: 你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n **是数组大小。
1. 生活案例:人气练习生选拔赛
想象你是一个选秀节目的制片人,你需要从 100 个练习生(数字)中选出 人气最高(出现频率最高) 的 个人。
-
第一步:计票(统计频率)
你让工作人员统计每个练习生得了多少票。结果记录在笔记本上(
Map):练习生 A 得了 10 票,练习生 B 得了 3 票…… -
第二步:搭建一个“底线舞台”(小顶堆)
你只准备了 个座位的舞台。规则很残酷:如果你想上台,你的票数必须比台上那个“票数最少的人”还要多。
- 一旦台上坐满了 个人,新来的人如果票数更多,就把那个票数最少的踢下去,自己坐上去。
- 这个舞台的特点是:票数最少的人永远坐在舞台最显眼的位置(堆顶) ,方便随时被踢走。
-
第三步:结果公示
最后留在舞台上的这 个人,就是人气最高的前 名。
2. 代码解析与“生活化”注释
这段代码展示了如何手动实现一个“小顶堆”来维护这前 个高频元素。
JavaScript
var topKFrequent = function(nums, k) {
// 1. 计票:使用 Map 统计每个数字出现的次数
let map = new Map();
for(let num of nums){
map.set(num, (map.get(num) || 0) + 1);
}
// 2. 准备舞台:heap 数组用来模拟“堆”
let heap = [];
// 这是一个“向上调整”的过程:新成员入场,根据票数往上爬
let push = (val) => {
heap.push(val);
let cur = heap.length - 1;
while(cur > 0){
// 找到他的“长辈”(父节点)
let parent = Math.floor((cur - 1) / 2);
// 如果他的票数比长辈还少,他就得跟长辈换位置(小顶堆:小的在上)
if(heap[cur][1] < heap[parent][1]){
[heap[cur], heap[parent]] = [heap[parent], heap[cur]];
cur = parent;
} else break;
}
};
// 这是一个“向下调整”的过程:淘汰了最弱的人,让剩下的人重新排座次
let pop = () => {
let top = heap[0]; // 拿走舞台上票数最少的人
heap[0] = heap.pop(); // 让队伍最后一个人先顶上去
let cur = 0;
while(cur * 2 + 1 < heap.length){
let left = cur * 2 + 1, right = cur * 2 + 2, smaller = left;
// 看看两个“后辈”里谁的票数更少
if(right < heap.length && heap[right][1] < heap[left][1]) smaller = right;
// 如果顶上来的人票数比后辈多,他得往下挪
if(heap[smaller][1] < heap[cur][1]){
[heap[cur], heap[smaller]] = [heap[smaller], heap[cur]];
cur = smaller;
} else break;
}
return top;
};
// 3. 开始比赛
for (let entry of map.entries()) {
push(entry); // 尝试让练习生上台
// 关键逻辑:如果舞台坐不下了(超过K个人),把票数最少(堆顶)的人踢走
if (heap.length > k) pop();
}
// 最后舞台上剩下的就是我们要的人,把他们的名字(数字本身)取出来
return heap.map(x => x[0]);
};
3. 为什么代码这样写?(核心优势)
-
为什么不用全部排序?
- 如果我们把所有练习生按票数排序,时间复杂度是 。
- 使用堆,我们只需要维护一个大小为 的小舞台,复杂度是 。当 远小于 时,这种方法快得多!
-
小顶堆的妙用:
- 我们要找的是“最高频”,却用“小顶堆”?
- 生活化理解:因为我们要不断地把“最不给力的人”踢走,所以必须让那个票数最低的人待在最容易操作的位置(堆顶)。
总结
这道题是数据结构面试中的常客。它综合了 哈希表统计 和 堆排序思想。
let n = nums.length;
let map = new Map();
for (let num of nums) {
count = (map.get(num) || 0) + 1;
map.set(num,count);
}
let arr=[...map.entries()];
arr.sort((a,b)=>b[1]-a[1]);
return arr.slice(0,k).map(item=>item[0])
};