持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情
给你一个整数数组nums和一个整数k,请你返回其中出现频率前k高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
1 <= nums.length <= 105k的取值范围是 [1, 数组中不相同的元素的个数]- 题目数据保证答案唯一,换句话说,数组中前
k个高频元素的集合是唯一的
解题思路:最小堆
这道题的思路很好写
- 统计元素和元素在数组中出现的次数
- 排序
- 找出前k个高频元素
我们可以使用
map统计元素出现的次数,map的key用来存放元素的数值,value用来存放元 素在数组中出现的次数
但是到了第二步,对map排序,这块的代码就不好写了,我是考虑过用快排来排序的,但使用快排要将map转换为vector的结构,然后对整个数组进行排序,复杂度太高了
在看其他人写的题解过程中,我发现可以使用一种叫优先级队列的容器适配器来进行排序
优先级队列是什么呢,其实就是披着队列皮的
堆,尽管它叫做优先级队列,但堆并不是队列因为队列中允许的操作是先进先出
(FIFO),在队尾插入元素,在队头取出元素。而堆虽然在堆底插入元素,在堆顶取出元素,但是堆中元素的排列不是按照到来的先后顺序,而是按照一定的优先顺序排列的。
那么堆又是什么呢,堆就是一个完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。
如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
从小到大排就是小顶堆,从大到小排就是大顶堆。
那么本题是要使用最小堆呢还是最大堆呢?
答案是使用最小堆,我刚开始看到的时候也有点想不通,题目要求我们返回高频元素,按理说应该选择从大到小的大顶堆啊,怎么会使用小顶堆呢。随着我往下看,才明白了要使用小顶堆的原因
使用最小堆的原因
如果使用大顶堆,那在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。
所以我们要用小顶堆,要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
具体代码:(JAVA实现)
public static int[] topKFrequent(int[] nums,int k ){
//用mao来存放元素出现频率
HashMap<Integer, Integer> hashMap = new HashMap<>();
//遍历过程
for (int i = 0; i < nums.length; i++) {
if (hashMap.containsKey(nums[i])) {
hashMap.put(nums[i],hashMap.get(nums[i]) + 1);
}else {
hashMap.put(nums[i],1);
}
}
//遍历map,用最小堆保存频率最大的k个元素
PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return hashMap.get(o1) - hashMap.get(o2);
}
});
//用固定大小为k的小顶堆,扫面所有频率的数值
for (Integer key : hashMap.keySet()) {
if (queue.size() < k) {
queue.add(key);
}else if (hashMap.get(key) > hashMap.get(queue.peek())) {
queue.remove();
queue.add(key);
}
}
//找出前K个高频元素,将最小堆里的元素放进新定义的数组里
int[] ints = new int[k];
int count = 0;
while (!queue.isEmpty()) {
ints[count++] = queue.poll();
}
return ints;
}
复杂度分析
- 时间复杂度:
O(n log k) - 空间复杂度:
O(n),n为哈希表长度