1. 程式人生 > >LeetCode【347】 Top K Frequent Elements

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;
}