LeetCode 347. Top K Frequent Elements
Woodyiiiiiii opened this issue · comments
Given a non-empty array of integers, return the k most frequent elements.
Example 1:
Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]
Example 2:
Input: nums = [1], k = 1
Output: [1]
Note:
- You may assume k is always valid, 1 ≤ k ≤ number of unique elements.
- Your algorithm's time complexity must be better than O(n log n), where n is the array's size.
- It's guaranteed that the answer is unique, in other words the set of the top k frequent elements is * unique.
- You can return the answer in any order.
题目要求的是返回前K个经常出现的数,是求前K个最大的数的升级版(书上的例题)。
这类问题可以创建一个最小(大)堆,达到O(nlogn)的时间复杂度。我们可以利用HashMap保存数组中元素和它出现次数的映射,作为比较关系,建立堆。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
int n = nums.length;
int[] res = new int[k];
int i = 0, j = 0;
HashMap<Integer, Integer> map = new HashMap();
// (nums, times) put into map
for (i = 0; i < n; ++i) {
if (!map.containsKey(nums[i]))
map.put(nums[i], 1);
else {
int t = map.get(nums[i]);
map.replace(nums[i], t + 1);
}
}
// initalize array res
HashSet<Integer> kNums = new HashSet();
for (i = 0; i < n && j < k; ++i) {
if (!kNums.contains(nums[i])) {
kNums.add(nums[i]);
// res[j++] = nums[i];
heapInsert(res, nums[i], map, j);
++j;
}
}
// min heap
for (; i < n; ++i) {
if (!kNums.contains(nums[i]) && map.get(nums[i]) > map.get(res[0])) {
kNums.add(nums[i]);
res[0] = nums[i];
heapify(res, 0, k, map);
}
}
return res;
}
public void heapInsert(int[] heap, int value,
HashMap<Integer, Integer> map, int index) {
heap[index] = value;
while (index != 0) {
int parent = (index - 1) / 2;
if (map.get(heap[parent]) > map.get(heap[index])) {
int t = heap[parent];
heap[parent] = heap[index];
heap[index] = t;
index = parent;
}
else break;
}
}
public void heapify(int[] heap, int index, int k, HashMap<Integer, Integer> map) {
int left = index * 2 + 1, right = index * 2 + 2;
int smallest = index;
while (left < k) {
if (map.get(heap[left]) < map.get(heap[index]))
smallest = left;
if (right < k && map.get(heap[smallest]) > map.get(heap[right]))
smallest = right;
if (smallest != index) {
int t = heap[smallest];
heap[smallest] = heap[index];
heap[index] = t;
}else {
break;
}
index = smallest;
left = index * 2 + 1;
right = index * 2 + 2;
}
}
}
当然还可以使用Java集合类中的优先队列PriorityQueue,实际上就是最大(小)堆。
我卡在了如何定义构造器创建合适的优先队列,然后看了如下大佬的做法:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//check input
if(nums == null || nums.length == 0 || k < 1) return new int[1];
//HashMap to count frequency of the elements
//Key as number and values as frequency or counts
Map<Integer, Integer> map = new HashMap<>();
//count number of frequencies for given array
for(int num: nums) {
map.put(num, map.getOrDefault(num,0) + 1);
}
//priority Queue(Max heap) to with custome comparator (Descending order)
Queue <Integer> queue = new PriorityQueue<>((e1,e2) -> map.get(e2) - map.get(e1));
//Add all elements(which will sort via their frequency or counts)
queue.addAll(map.keySet());
int[] result = new int[k];
int i = 0;
while(!queue.isEmpty()) {
result[i++] = queue.poll();
if(i==k) break;
}
return result;
}
}
这段代码里有很多我没见过的并且很有意思的写法:
- map.getOrDefault(Object key, V defaultValue)方法官方讲解是"Returns the value to which the specified key is mapped, or defaultValue if this map contains no mapping for the key.",即如果Map中有Key,则返回配对的Value,否则返回参数defalutValue,而加上map.put()方法,意味着如果Map中有Key,则取出Value然后加一再更新(put方法有更新的作用:"If the map previously contained a mapping for the key, the old value is replaced"),如果没有Key,则将(Key, 1)加入Map中。
- new PriorityQueue<>((e1,e2) -> map.get(e2) - map.get(e1)),这里设计如何定义Map的键值对的构造器,有三种写法,可以看参考资料3。这里不同的是,Queue的类型是Integer类的,加入了Map来实现compare方法,并不是常规的Map.Entry<>。
- queue.addAll(map.keySet());参数是Collection类,可以一次性加入Collection类或实现了Collection类的子类。
2020/09/09:我又来重写了一遍:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
if(nums == null || nums.length == 0 || k < 1) {
return new int[1];
}
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
PriorityQueue<Map.Entry<Integer, Integer>> queue =
new PriorityQueue<>(new Comparator<Map.Entry<Integer, Integer>>() {
@Override
public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
if (o1.getValue().equals(o2.getValue())) {
return o1.getKey() - o2.getKey();
}
return o2.getValue() - o1.getValue();
}
});
queue.addAll(map.entrySet());
int[] res = new int[k];
int i = 0;
while ((k--) != 0 && !queue.isEmpty()) {
res[i++] = queue.poll().getKey();
}
return res;
}
}
既然可以用优先队列,那也可以使用TreeMap,TreeMap的按键排序简单,但按值排序需要将其转化为list,再使用Collection.sort进行排序:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//check input
if(nums == null || nums.length == 0 || k < 1) return new int[1];
//HashMap to count frequency of the elements
//Key as number and values as frequency or counts
Map<Integer, Integer> map = new TreeMap<Integer, Integer>();
Map<Integer, Integer> sortedMap = new LinkedHashMap<Integer, Integer>();
//count number of frequencies for given array
for(int num: nums) {
map.put(num, map.getOrDefault(num,0) + 1);
}
// map converted to list and sort
List<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer, Integer>> (map.entrySet());
Collections.sort(list,new Comparator<Map.Entry<Integer,Integer>>() {
@Override
public int compare(Map.Entry<Integer, Integer> o1,
Map.Entry<Integer, Integer> o2) {
// return o1.getValue().compareTo(o2.getValue());
return o2.getValue() - (o1.getValue());
}
});
// 使用LinkedHashMap确保真正排好序了
Iterator<Map.Entry<Integer, Integer>> iter = list.iterator();
Map.Entry<Integer, Integer> tmpEntry = null;
while (iter.hasNext()) {
tmpEntry = iter.next();
sortedMap.put(tmpEntry.getKey(), tmpEntry.getValue());
}
int[] result = new int[k];
int i = 0;
for (Map.Entry<Integer, Integer> entry : sortedMap.entrySet()) {
result[i++] = entry.getKey();
if (i == k) break;
}
return result;
}
}
这段代码有两个Map,TreeMap和LinkedHashMap,因为Collections.sort排好序后TreeMap顺序仍然不变,所以需要将排好序的list中元素放进LinkedHashMap中确保排好序。LinkedHashMap的优点在于,它不同于HashMap根据HashCode值存储数据,它保持了插入顺序,但同时速度慢,相应知识可以查看参考资料5。这样平均运行时间增大了很多,所以还不如用PriorityQueue。
还有一种方法是利用桶排序的方法,详细做法可以看下方参考资料6。
参考资料:
- https://leetcode.com/problems/top-k-frequent-elements/
- https://leetcode.com/problems/top-k-frequent-elements/discuss/615079/Java-10ms-using-priority-queue-Max-Heap-and-hashmap
- https://zhuanlan.zhihu.com/p/50104612
- https://blog.csdn.net/u011781521/article/details/80112023
- https://blog.csdn.net/shengqianfeng/article/details/79939414
- grandyang/leetcode#347