题目
给你一个整数数组
nums
和一个整数k
,请你返回其中出现频率前k
高的元素。你可以按任意顺序返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
解答
这一题可以在LeetCode 热题 100 - 堆 - 数组中的第K个最大元素 - javascript的基础上进行进一步的编写。
- 用map统计每个元素出现的次数。key为元素,value为出现次数。
- 然后将map中的key按照其value进行构建小顶堆(堆中元素个数为k)。最后返回该小顶堆就可以。
需先了解的知识点:
- 大顶堆:任何一个节点的左右子节点都比该节点的值小。
- 小顶堆:任何一个节点的左右子节点都比该节点的值大。
- 对于一个节点索引为i,它的父节点的索引为Math.floor((i-1)/2)或者Math.floor(i/2)-1,它的左子节点索引为2i+1,右子节点索引为2i+2
算法思路
-
统计频率:使用
Map
统计每个数字出现的频率。 -
特殊情况处理:如果不同数字的个数 ≤ K,直接返回所有数字。
-
构建最小堆:维护一个大小为 K 的最小堆,堆顶是当前 K 个高频元素中频率最小的元素。
-
遍历剩余元素:
-
如果当前元素的频率 > 堆顶元素的频率,替换堆顶并调整堆。
-
否则,跳过该元素。
-
-
返回堆中的元素:最终堆中存储的就是前 K 个高频元素。
示例分析
输入
nums = [1,1,1,2,2,3], k = 2
步骤
-
统计频率:
-
map = {1:3, 2:2, 3:1}
-
-
初始化堆:
-
先填充
heap = [1, 2]
(前 K 个元素)。 -
调用
buildHeap
调整堆:-
1
(频率=3)和2
(频率=2)比较,2
更小,堆顶是2
。
-
-
-
遍历剩余元素:
-
3
(频率=1)和堆顶2
(频率=2)比较,3
频率更低,跳过。
-
-
返回堆:
-
heap = [1, 2]
(频率最高的两个元素)。
-
var topKFrequent = function (nums, k) {
// 1. 使用Map统计每个数字出现的频率
let map = new Map();
for (let num of nums) {
if (map.has(num)) {
// 如果数字已存在,频率+1
map.set(num, map.get(num) + 1);
} else {
// 新数字,初始化频率为1
map.set(num, 1);
}
}
// 2. 特殊情况处理:如果不同数字数量 ≤ k,直接返回所有key
if (map.size <= k) {
return [...map.keys()];
}
// 3. 初始化最小堆(用于存储前K个高频元素)
let heap = [];
let i = 0; // 计数器
// 4. 遍历Map中的每个数字及其频率
map.forEach((frequency, num) => {
if (i < k) {
// 4.1 先填充堆到k个元素
heap.push(num);
// 当堆填满k个元素时,构建最小堆
if (i === k - 1) buildHeap(heap, map, k);
} else if (frequency > map.get(heap[0])) {
// 4.2 如果当前数字频率 > 堆顶元素频率
// 替换堆顶元素(移除最小频率元素)
heap[0] = num;
// 从堆顶开始调整堆
heapify(heap, map, k, 0);
}
// 增加i
i++;
});
// 5. 最终堆中存储的就是前K个高频元素
return heap;
};
// 构建小顶堆
function buildHeap(heap, map, k) {
// 堆大小为1时无需调整
if (k === 1) return;
// 从最后一个非叶子节点开始,向前逐个堆化
for (let i = Math.floor(k / 2) - 1; i >= 0; i--) {
heapify(heap, map, k, i);
}
}
// 调整堆为小顶堆
function heapify(heap, map, k, i) {
while (true) {
let minIndex = i; // 假设当前节点是最小频率的节点
// 如果左子节点存在且频率更小,更新minIndex
if (2 * i + 1 < k && map.get(heap[2 * i + 1]) < map.get(heap[minIndex])) {
minIndex = 2 * i + 1;
}
// 如果右子节点存在且频率更小,更新minIndex
if (2 * i + 2 < k && map.get(heap[2 * i + 2]) < map.get(heap[minIndex])) {
minIndex = 2 * i + 2;
}
// 如果最小频率节点不是当前节点
if (minIndex !== i) {
// 交换当前节点与最小频率节点
[heap[i], heap[minIndex]] = [heap[minIndex], heap[i]];
// 继续向下调整
i = minIndex;
} else {
// 当前节点已经是最小频率节点,调整结束
break;
}
}
}