LeetCode【347】 Top K Frequent Elements
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]
思路:
1、首先遍歷陣列,使用HashMap freq的儲存每個數出現的頻率——freq(num, 頻率);
2、遍歷map,對頻率進行排序,這裡使用優先佇列priorityQueue的方式維護出現頻率最多的k個數
a.PriorityQueue pq的泛型為自定義的一個Pair類, pair(頻率,num),因為要對頻率進行比較,因此頻率就作為第一個數
b.優先佇列用最小堆實現方式,只儲存k個數,隊首即為頻率最小的pair
c.遍歷map,當pq的size小於k值時一直往裡邊新增pair,當=k時獲得隊首pair,比較pair的頻率和此時entry的值,前者小則 poll,offer進該entry(也就是一直維護pq為頻率最多的k個數)
3、將pq中的值放入結果list中。
程式碼:
class Solution { public List<Integer> topKFrequent(int[] nums, int k) { //1. traversing the nums to get freq of every nums Map<Integer, Integer> freq = new HashMap<>(); for(int num : nums){ freq.put(num, freq.getOrDefault(num, 0) + 1); } //2. sort the freq,使用最小堆來維護出現頻率最多的k個數 PriorityQueue<Pair> pq = new PriorityQueue<>(k, idComparator); for(Map.Entry<Integer, Integer> entry : freq.entrySet()){ if(pq.size()==k){ //取出pq中出現頻率最少的元素,然後刪除,再新增進此時的entry if(pq.peek().getFreq() < entry.getValue()){ pq.poll(); pq.offer(new Pair(entry.getValue(), entry.getKey())); } }else{ pq.offer(new Pair(entry.getValue(), entry.getKey())); } } System.out.println(pq.size()); //3. 結果 List<Integer> res = new ArrayList<>(); while(!pq.isEmpty()){ res.add(pq.peek().getNum()); pq.poll(); } return res; } //自定義匿名比較器(最小堆的方式) public static Comparator<Pair> idComparator = new Comparator<Pair>(){ public int compare(Pair p1, Pair p2){ return p1.getFreq() - p2.getFreq(); } }; } class Pair{ private int freq; private int num; Pair(int freq, int num){ this.freq = freq; this.num = num; } public void setFreq(int a){ this.freq = a; } public int getFreq(){ return freq; } public void setNum(int b){ this.num = b; } public int getNum(){ return num; } }
時間複雜度:遍歷陣列O(n),優先佇列O(nlogk),總:O(nlogk)
知識點:
1、使用HashMap儲存某資料中元素出現的次數。
2、優先佇列:維護前k大哥元素或前k小個元素。
3、遍歷Map的方法:
a.for-each迴圈中使用entry遍歷
Map<Integer, Integer> map = new HashMap<Integer, Integer>(); for (Map.Entry<Integer, Integer> entry : map.entrySet()) { entry.getKey(); entry.getValue(); }
b. 在for-each中迴圈keys或values
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int k: map.keySet()) {
int value = map.get(k);
}
for (int v: map.values()) {
...
}
c.通過iterator
HashMap<Integer, Integer> map = new HashMap<>();
Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
while(it.hasNext()){
Entry<Integer, Integer> entry = it.next();
entry.getKey();
entry.getValue();
}
其他:
看了discuss中快的部分答案,使用桶排序完成對頻率的排序。
思路:
排序部分:按資料元素出現的頻次進行排序,那麼“桶”的數量範圍是可以確定的——桶的數量小於等於給定陣列元素的個數。編號為i的桶用於存放陣列中出現頻次為i的元素——即編號為i的桶存放map中值等於i的鍵。
排序完成後,編號大的桶中元素出現的頻次高,因此,我們“逆序”(先取桶編號大的桶的元素)獲取桶中資料,直到獲取資料的個數等於k。
discuss中程式碼:
public List<Integer> topKFrequent(int[] nums, int k) {
List<Integer>[] bucket = new List[nums.length + 1];
Map<Integer, Integer> frequencyMap = new HashMap<Integer, Integer>();
for (int n : nums) {
frequencyMap.put(n, frequencyMap.getOrDefault(n, 0) + 1);
}
for (int key : frequencyMap.keySet()) {
int frequency = frequencyMap.get(key);
if (bucket[frequency] == null) {
bucket[frequency] = new ArrayList<>();
}
bucket[frequency].add(key);
}
List<Integer> res = new ArrayList<>();
for (int pos = bucket.length - 1; pos >= 0 && res.size() < k; pos--) {
if (bucket[pos] != null) {
res.addAll(bucket[pos]);
}
}
return res;
}