题目:
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
思路:这个我直接从leetcode的评论中找到答案,一共是两种,涉及到hash统计出现频数,堆排序和桶排序,先把大神们的代码写在这里。
代码一来自leetcode作者::MisterBooo
代码1思路:
PriorityQueue 一个基于优先级的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象(没有实现Comparable接口的对象)。最小堆是经过排序的完全二叉树,其中任意非终端节点的值均不大于左右节点的值。
复杂度分析:
时间复杂度:O(nlogk), n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);接着,遍历用于存储元素频率的 map,如果元素的频率大于最小堆中顶部的元素,则将顶部的元素删除并将该元素加入堆中,**这里维护堆的数目是 k **,所以这一系列操作的时间复杂度是 O(nlogk)的;因此,总的时间复杂度是 O(nlogk) 。
空间复杂度:O(n),最坏情况下(每个元素都不同),map 需要存储 n 个键值对,优先队列需要存储 k个元素,因此,空间复杂度是 O(n)。
我自己写了代码1,但是出现了好多问题,一个是HashMap中的好几个方法都不怎么熟,不会用;除此之外的使用优先队列构造最小堆也不怎么会写;还有就是我自己写代码的习惯问题了,总是写错单词,这种调试起来非常麻烦,特变难找到,建议写完代码之后还是整体自己先检查一遍,重点看有没有拼错。
peek():用于检索或获取Queue的第一个元素或Queue头部的元素。检索到的元素不会从队列中删除或删除。
containsKey():是否包含某一个key
map.keySet():HashMap中取出所有键的方法。
Map集合中提供了两种取出方式:
<1>. 返回值类型Set<k> 方法是: keySet() :返回此映射中包含的键的 Set 视图
将map中所有的键存入到Set集合,因为set具备迭代器,所有迭代方式取出所有的键
再根据get()方法 ,获取每一个键对应的值
<2>. 返回值类型:Set<Map.Entry<K,V>>方法是:entrySet()
取出的是关系,关系中包含key和value,其中Map.Entry<k,V>来表示这种数据类型
即:将map集合中的映射关系存入到set集合中,这个关系的数据类型为:Map.Entry
代码1:
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
HashMap<Integer,Integer> map = new HashMap();
for(int num : nums){
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
}
else
{
map.put(num, 1);
}
}
// 遍历map,用最小堆保存频率最大的k个元素,默认的情况下就是实现的小顶堆。
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override public int compare(Integer a, Integer b) {
return map.get(a) - map.get(b);
}
});
for (Integer key : map.keySet()) {
if (pq.size() < k) {
pq.add(key);
}
else if (map.get(key) > map.get(pq.peek())) {
pq.remove(); pq.add(key);
}
}
// 取出最小堆中的元素
List<Integer> res = new ArrayList<>();
while (!pq.isEmpty()) {
res.add(pq.remove());
}
return res;
}
}
代码二来自大神CYC2018。思路是使用桶排序。
复杂度分析
时间复杂度:O(n), n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);桶的数量为 n + 1,所以桶排序的时间复杂度为 O(n);因此,总的时间复杂度是 O(n)。
空间复杂度:很明显为 O(n)
思路:这个是使用桶排序,首先第一步还是使用HashMap统计出现的频率。然后创建一个桶,将出现次数为i的数字放置在相应的桶中的list里面,最后从后往前进行遍历,知道取出k个数字。
特别注意,方法ketSet()中的第一个字母k是不大写的,今天找了好长时间的错误,要气吐血。。。。。
代码2:
public List<Integer> topKFrequent(int[] nums, int k) {
Map<Integer, Integer> frequencyForNum = new HashMap<>();
for (int num : nums) { //这部分应该是频率统计
frequencyForNum.put(num, frequencyForNum.getOrDefault(num, 0) + 1);
}
List<Integer>[] buckets = new ArrayList[nums.length + 1];
for (int key : frequencyForNum.keySet()) {
int frequency = frequencyForNum.get(key);
if (buckets[frequency] == null) {
buckets[frequency] = new ArrayList<>();
}
buckets[frequency].add(key);
}
List<Integer> topK = new ArrayList<>();
for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) {
if (buckets[i] == null) {
continue;
}
if (buckets[i].size() <= (k - topK.size())) {
topK.addAll(buckets[i]);
} else {
topK.addAll(buckets[i].subList(0, k - topK.size()));
}
}
return topK;
}