【動畫】求解前 K 個高頻元素的三種解法

本文首發於公衆號「五分鐘學算法」,是圖解 LeetCode 系列文章之一。java

我的網站:www.cxyxiaowu.comgit

今天分享的題目來源於 LeetCode 上第 347 號問題:前 K 個高頻元素。題目難度爲 Medium,目前經過率爲 56.9% 。github

題目描述

給定一個非空的整數數組,返回其中出現頻率前 k 高的元素。算法

示例 1:數組

輸入: nums = [1,1,1,2,2,3], k = 2
輸出: [1,2]
複製代碼

示例 2:bash

輸入: nums = [1], k = 1
輸出: [1]
複製代碼

說明:數據結構

  • 你能夠假設給定的 k 老是合理的,且 1 ≤ k ≤ 數組中不相同的元素的個數。
  • 你的算法的時間複雜度必須優於 O(n log n) , n 是數組的大小。

題目解析

解法一:粗暴排序法

最簡單粗暴的思路就是 使用排序算法對元素按照頻率由高到低進行排序,而後再取前 k 個元素。ide

如下十種排序算法,任你挑選!優化

能夠發現,使用常規的諸如 冒泡、選擇、甚至快速排序都是不知足題目要求,它們的時間複雜度都是大於或者等於 O(n log⁡n) ,而題目要求算法的時間複雜度必須優於 O(n log n) 。網站

複雜度分析

  • 時間複雜度:O(nlogn),n 表示數組長度。首先,遍歷一遍數組統計元素的頻率,這一系列操做的時間複雜度是 O(n);接着,排序算法時間複雜度爲O(nlogn) ;所以總體時間複雜度爲 O(nlogn) 。
  • 空間複雜度:O(n),最極端的狀況下(每一個元素都不一樣),用於存儲元素及其頻率的 Map 須要存儲 n 個鍵值對。

解法二:最小堆

題目最終須要返回的是前 k 個頻率最大的元素,能夠想到藉助堆這種數據結構,對於 k 頻率以後的元素不用再去處理,進一步優化時間複雜度。

具體操做爲:

  • 藉助 哈希表 來創建數字和其出現次數的映射,遍歷一遍數組統計元素的頻率
  • 維護一個元素數目爲 k 的最小堆
  • 每次都將新的元素與堆頂元素(堆中頻率最小的元素)進行比較
  • 若是新的元素的頻率比堆頂端的元素大,則彈出堆頂端的元素,將新的元素添加進堆中
  • 最終,堆中的 k 個元素即爲前 k 個高頻元素

堆中的元素就是前 k 個頻率最大的元素

代碼以下:

class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        // 使用字典,統計每一個元素出現的次數,元素爲鍵,元素出現的次數爲值
        HashMap<Integer,Integer> map = new HashMap();
        for(int num : nums){
            if (map.containsKey(num)) {
               map.put(num, map.get(num) + 1);
             } else {
                map.put(num, 1);
             }
        }
        // 遍歷map,用最小堆保存頻率最大的k個元素
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return map.get(a) - map.get(b);
            }
        });
        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);
            }
        }
        // 取出最小堆中的元素
        List<Integer> res = new ArrayList<>();
        while (!pq.isEmpty()) {
            res.add(pq.remove());
        }
        return res;
    }
}

複製代碼

複雜度分析

  • 時間複雜度:O(nlogk), n 表示數組的長度。首先,遍歷一遍數組統計元素的頻率,這一系列操做的時間複雜度是 O(n);接着,遍歷用於存儲元素頻率的 map,若是元素的頻率大於最小堆中頂部的元素,則將頂部的元素刪除並將該元素加入堆中,**這裏維護堆的數目是 k **,因此這一系列操做的時間複雜度是 O(nlogk)的;所以,總的時間複雜度是 O(nlog⁡k) 。
  • 空間複雜度:O(n),最壞狀況下(每一個元素都不一樣),map 須要存儲 n 個鍵值對,優先隊列須要存儲 k個元素,所以,空間複雜度是 O(n)。

解法三:桶排序法

首先依舊使用哈希表統計頻率,統計完成後,建立一個數組,將頻率做爲數組下標,對於出現頻率不一樣的數字集合,存入對應的數組下標便可。

代碼實現以下:

//基於桶排序求解「前 K 個高頻元素」
class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        List<Integer> res = new ArrayList();
        // 使用字典,統計每一個元素出現的次數,元素爲鍵,元素出現的次數爲值
        HashMap<Integer,Integer> map = new HashMap();
        for(int num : nums){
            if (map.containsKey(num)) {
               map.put(num, map.get(num) + 1);
             } else {
                map.put(num, 1);
             }
        }
        
        //桶排序
        //將頻率做爲數組下標,對於出現頻率不一樣的數字集合,存入對應的數組下標
        List<Integer>[] list = new List[nums.length+1];
        for(int key : map.keySet()){
            // 獲取出現的次數做爲下標
            int i = map.get(key);
            if(list[i] == null){
               list[i] = new ArrayList();
            } 
            list[i].add(key);
        }
        
        // 倒序遍歷數組獲取出現順序從大到小的排列
        for(int i = list.length - 1;i >= 0 && res.size() < k;i--){
            if(list[i] == null) continue;
            res.addAll(list[i]);
        }
        return res;
    }
}
複製代碼

複雜度分析

  • 時間複雜度:O(n), n 表示數組的長度。首先,遍歷一遍數組統計元素的頻率,這一系列操做的時間複雜度是 O(n);桶的數量爲 n + 1,因此桶排序的時間複雜度爲 O(n);所以,總的時間複雜度是 O(n)。
  • 空間複雜度:很明顯爲 O(n)
相關文章
相關標籤/搜索