1.題目描述
2.思路
(1)這里定義了一個小根堆(最小堆),根據元素的頻率從小到大排序。小根堆原理:堆頂是最小值,每次插入或刪除操作會保持堆的有序結構(常用二叉堆實現)。
比如,map.get(a) - map.get(b) 表示:
頻率小的排在堆頂;
當堆滿 k 個元素后,就可以比較新元素與堆頂的大小,進行替換。
(2)這個循環遍歷 map 中的所有 key,目的是找出頻率前 k 高的元素。
如果堆還沒滿,直接加入;
如果堆滿了,比較當前元素的頻率和堆頂元素頻率:
當前元素更高,則替換堆頂。
繼續例子(k = 2)
map: {1=3, 2=2, 3=1}
按順序加入:
加入 1,堆 = [1]
加入 2,堆 = [1, 2](根據頻率排序,2 的頻率低,堆頂是 2)
處理 3(頻率 1):
頻率小于堆頂 2 → 不加入
最終堆中是 [1, 2]
🧩 舉個例子來直觀理解:
(1)輸入:nums = [1, 1, 1, 2, 2, 3],k = 2
(2)頻率統計結果:map = {1=3, 2=2, 3=1}
維護一個小根堆 pq,大小最多為 2(k=2):
加入 1(頻率3)→ pq = [1]
加入 2(頻率2)→ pq = [2, 1](按頻率排序,堆頂是頻率最小的)
嘗試加入 3(頻率1):
頻率 1 < 堆頂 2 的頻率 ? 不加入
現在 pq 中是 [2, 1],是頻率前 2 高的元素!
取出堆中元素:
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = pq.remove(); // remove 堆頂元素
}
return result;
雖然 remove() 是按頻率從小到大彈出,但我們關心的是內容是這 k 個元素本身是否是頻率最高的
它不保證從大到小的順序(如果你想要排好順序,可以再排序)
總結這段邏輯:
小根堆始終維護的是「當前頻率最高的前 k 個元素」;
取出這些元素即可完成任務;
堆頂是這 k 個元素中最小的,所以我們才用小根堆。
3.代碼實現
class Solution {public int[] topKFrequent(int[] nums, int k) {//使用字典,統計每個元素出現的次數,元素為鍵,元素出現的次數為值HashMap<Integer, Integer> map = new HashMap<>();for (int num : nums) {if(map.containsKey(num)){//如果字典元素在后續遍歷的過程中又再次出現,直接次數+1map.put(num, map.get(num) + 1);}else{//如果該字典元素是首次出現。map.put(num, 1);}}//遍歷map,用最小堆保存頻率最大的k個元素//優先級隊列的底層原理就是堆PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {public int compare(Integer a, Integer b) {return map.get(a) - map.get(b); // 出現次數小的排前面(小根堆)}});// 遍歷 map 的 key 值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); // 加入當前頻率更高的元素}}//取出最小堆的元素int[] result=new int[k];for(int i=0;i<k;i++){result[i]=pq.remove();}return result;//pq.remove() 會返回堆頂元素,并從堆中刪除該元素;}
}