leetcode347 —— n中topK && PriorityQueue(Heap) && Map遍历
题目要求:求前K个最频繁出现的数字。
1.很容易想到,使用HashMap<Integer,Integer>来存储<number,frequency>键值对
1 int n = nums.length; 2 Map<Integer, Integer> map = new HashMap<>(n); 3 4 for (int num : nums) { 5 map.put(num, map.getOrDefault(num, 0) + 1); 6 }
2.接下来非常自然的思路就是:
- 对<number,frequency>按照frequency进行降序排序
- 返回前k个<number,frequency>的number
根据题目的黑字提示:“你可以按 任意顺序 返回答案。”。可以计算出第K频繁的数的frequency,接着遍历map,返回所有frequency >= Kth的number即可:
1 Integer[] temp = map.values().toArray(new Integer[map.size()]); 2 Arrays.sort(temp); 3 int[] ans = new int[k]; 4 int KthCount = temp[temp.length - k], i = 0; 5 6 for (Map.Entry<Integer, Integer> kv : map.entrySet()) { 7 if (kv.getValue() >= KthCount) { 8 ans[i++] = kv.getKey(); 9 } 10 }
因为时间超过5%,想到了对map遍历时是否可以直接遍历map中的键值对,于是:
1 for (Map.Entry<Integer, Integer> kv : map.entrySet()) { 2 if (kv.getValue() >= KthCount) { 3 ans[i++] = kv.getKey(); 4 } 5 }
进阶的时间要求想不出来,于是看了题解,有了以下收获:
题目最终需要返回的是前 k 个频率最大的元素,可以想到借助堆这种数据结构,对于 k 频率之后的元素不用再去处理,进一步优化时间复杂度。
- 求前 k 大,用小根堆,求前 k 小,用大根堆
-
PriorityQueue<int[]> minHeap = new PriorityQueue<>(k, new Comparator<int[]>() { @Override public int compare(int[] o1, int[] o2) { //升序 return o1[1] - o2[1]; //降序 return o2[1] - o1[1]; } });
-
- topk (前k大)用小根堆,维护堆大小不超过 k 即可。每次压入堆前和堆顶元素比较,如果比堆顶元素还小,直接扔掉,否则压入堆。检查堆大小是否超过 k,如果超过,弹出堆顶。复杂度是 nlogk
- 避免使用大根堆,因为你得把所有元素压入堆,复杂度是 nlogn,而且还浪费内存。如果是海量元素,那就挂了。
PriorityQueue<int[]> minHeap = new PriorityQueue<>(k, new Comparator<int[]>() { @Override public int compare(int[] o1, int[] o2) { return o1[1] - o2[1]; } }); int count = 0 for (Map.Entry<Integer, Integer> kv : map.entrySet()) { //前K个键值对不需要判断 if (count++ < k) { minHeap.add(new int[]{kv.getKey(), kv.getValue()}); } else if (kv.getValue() > minHeap.peek()[1]) { minHeap.poll(); minHeap.add(new int[]{kv.getKey(), kv.getValue()}); } }