目录
https://leetcode.com/problems/top-k-frequent-elements/
给定一个元素为整数的非空数组,返回出现次数最多的前k个元素。
一、问题描述
测试用例如下:
Example 1:
Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]
Example 2:
Input: nums = [1], k = 1
Output: [1]
由于数组不为空、k始终满足1<= k <= 不同的数字个数,所以不用考虑数组、k不合法的情况;另外,时间复杂度必须由于O(nlogn),其中n是数组的大小。
因为要找出出现频率最大的前k个元素,所以我们一开始要算出每个数字的出现频率,然后再想办法找出前k个(可以用堆、快速选择等方式实现)。
二、代码实现
1、堆
遍历所有元素,将它们添加到小顶堆中,同时始终维护小顶堆的元素个数为k(当超过k时删除出现次数最少的堆顶元素),当所有元素都遍历过后,堆中的k个元素就是出现频率最高的前k个元素。
class Solution {
//堆
public List<Integer> topKFrequent1(int[] nums, int k) {
//O(n)
HashMap<Integer, Integer> map = new HashMap<>();
for (int e : nums) {
if (map.containsKey(e)) {
map.put(e, map.get(e)+1);
} else {
map.put(e, 1);
}
}
//O(nlogk) 最坏情况下map中的元素个数等于nums的元素个数,因此这里为n
PriorityQueue<Integer> heap = new PriorityQueue<>((n1, n2) -> map.get(n1) - map.get(n2));
//PriorityQueue<Map.Entry<Integer, Integer>> minHeap =
//new PriorityQueue<>((a, b) -> Integer.compare(a.getValue(), b.getValue()));小顶堆
//PriorityQueue<Map.Entry<Integer, Integer>> maxHeap =
//new PriorityQueue<>((a,b)->(b.getValue()-a.getValue()));大顶堆
for (int key : map.keySet()) {
heap.add(key);
if (heap.size() > k) {
heap.poll();
}
}
//O(nlogk)
List<Integer> topK = new ArrayList<>(k);
while (!heap.isEmpty()) {
topK.add(heap.poll());
}
Collections.reverse(topK);
return topK;
}
}
2、桶排序的思想
创建一个数组将出现频率相同的所有元素存在下标为频率值的位置处。
class Solution {
//桶排序
public List<Integer> topKFrequent2(int[] nums, int k) {
//O(n)
HashMap<Integer, Integer> map = new HashMap<>();
for (int e : nums) {
map.put(e, map.getOrDefault(e, 0) + 1);
}
//桶排序的思想,相同频率的元素放在同一个桶 O(n)
List<Integer>[] frequencyBuckets = new List[nums.length + 1]; //频率的可能取值为[0, n]
for (int key : map.keySet()) {
int frequency = map.get(key);
if (frequencyBuckets[frequency] == null) {
frequencyBuckets[frequency] = new ArrayList<>();
}
frequencyBuckets[frequency].add(key);
}
//从后往前遍历各个频率值 O(n)
List<Integer> topK = new ArrayList<>();
for (int frequency=frequencyBuckets.length-1; frequency>0 && topK.size()<k; frequency--) {
if (frequencyBuckets[frequency] != null) {
for (int i=0; i<frequencyBuckets[frequency].size() && topK.size()<k; i++) {
topK.add(frequencyBuckets[frequency].get(i));
}
}
}
return topK;
}
}
上面采用数组来建立出现频率 -> 元素的映射,很容易出现许多空的桶,因此可以采用HashMap来代替数组。
class Solution {
//桶排序
public List<Integer> topKFrequent3(int[] nums, int k) {
Map<Integer, Integer> elementsToCount = new HashMap<>(); //elements -> frequency
Map<Integer, List<Integer>> countToElements = new HashMap<>(); //frequency -> elements
int maxCount = Integer.MIN_VALUE; //最大频率值,各个元素的频率的可能取值为[1, maxCount]
for (int e : nums) {
int newCount = elementsToCount.getOrDefault(e, 0) + 1;
elementsToCount.put(e, newCount);
maxCount = newCount > maxCount ? newCount : maxCount;
}
//桶排序的思想,相同频率的元素放在同一个桶中
for(Map.Entry<Integer,Integer> entry : elementsToCount.entrySet()) {
if (!countToElements.containsKey(entry.getValue())) {
countToElements.put(entry.getValue(), new ArrayList<>());
}
countToElements.get(entry.getValue()).add(entry.getKey());
}
List<Integer> result = new ArrayList<Integer>(k);
for (int i=maxCount; i>=0 && k>0 ; i--) {
List<Integer> eles = countToElements.get(i);
if (eles == null) {
continue;
}
for (int ele : eles) {
result.add(ele);
if(--k == 0)
return result;
}
}
return result; // k不合法的情况
}
}
利用TreeMap自动排序的特性
class Solution {
//桶排序
public List<Integer> topKFrequent4(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int n: nums){
map.put(n, map.getOrDefault(n,0)+1);
}
TreeMap<Integer, List<Integer>> freqMap = new TreeMap<>();
for (int num : map.keySet()){
int freq = map.get(num);
if(!freqMap.containsKey(freq)){
freqMap.put(freq, new LinkedList<>());
}
freqMap.get(freq).add(num);
}
List<Integer> res = new ArrayList<>();
while (res.size()<k){
Map.Entry<Integer, List<Integer>> entry = freqMap.pollLastEntry();
res.addAll(entry.getValue()); //有可能使res的大小超过k
}
return res;
}
}