程式碼隨想錄——棧與佇列8-前K個高頻元素

NeroMegumi發表於2024-10-30


法一、用陣列排序

思路

  1. 用map儲存元素和頻率關係

  2. 將元素和頻率的鍵值對pair作為vector的基本元素,以頻率為準進行從大到小的排序 —— O(nlogn)

  3. 輸出前K個pair的first,即數字本身

程式碼

class Solution {
   public:
       std::vector<int> topKFrequent(std::vector<int>& nums, int k) {
           std::unordered_map<int, int> frequencyMap;
           for (int num : nums) {
               frequencyMap[num]++;
           }
           std::vector<std::pair<int, int>> frequencyPairs;
           for (const auto& pair : frequencyMap) {
               frequencyPairs.push_back(pair);
           }
           std::sort(frequencyPairs.begin(), frequencyPairs.end(), [](const std::pair<int, int>& a, const std::pair<int, int>& b) {
               return a.second > b.second;
           });
           std::vector<int> result;
           for (int i = 0; i < k; ++i) {
               result.push_back(frequencyPairs[i].first);
           }
           return result;
       }
   };

這裡用到了lambda表示式定義比較規則。在 C++ 中,可以使用函式指標、函式物件(如類或結構體的過載運算子)或 lambda 表示式來定義比較規則,實現效果是相同的,但語法和使用方式略有不同。(下面的優先佇列就是用類定義比較規則)

但題目說明時間複雜度需要優於O(nlogn),而此方法使用的sort函式複雜度等於O(nlogn),因此還不夠好。

法二、堆

sort對所有N個元素進行了排序,而題目只要求輸出前K個高頻元素,那麼有沒有方法可以只排序K個元素,其他元素不參與排序呢(只與最大/最小值比較,不與其他K-1個元素比較大小),藉助堆可以實現。

堆的選擇:大根堆 or 小根堆

採用大根堆時,最大值在堆頂,若已有K個元素且需要更新時只能彈出堆頂,這樣就失去了最大值。

採用小根堆時,最小值在堆頂,更新堆時彈出的元素一定小於當前堆中所有元素,最後積累的K個元素一定是前K個最大元素。

因此採用小根堆。

優先佇列

在 C++ 中,<priority_queue> 是標準模板庫(STL)的一部分,用於實現優先佇列。

優先佇列是一種特殊的佇列,它允許我們快速訪問佇列中具有最高(或最低)優先順序的元素。

在 C++ 中,priority_queue 預設是一個最大堆,這意味著佇列的頂部元素總是具有最大的值。

priority_queue 是一個容器介面卡,它提供了對底層容器的堆操作。它不提供迭代器,也不支援隨機訪問。

引用自菜鳥教程

從這裡可以看出,在C++可以使用優先佇列實現堆,且在引數預設時預設構造大根堆。實現小根堆則要更改比較規則,這裡提供兩種語法。

1. 靜態函式

	static bool cmp(pair<int,int>& m,pair<int,int>& n){
			return m.second > n.second; //堆的實現就是n更小於是讓n作為父節點——維護最小堆
	}
	priority_queue<pair<int,int>,vector<pair<int,int>>,decltype(&cmp)> q(cmp);
  1. priority_queue
    這裡建立了一個優先佇列 q,其元素型別為 pair<int, int>
    vector<pair<int, int>>是用來儲存優先佇列元素的底層容器
    decltype(&cmp) 是一個型別推導,表示 cmp 函式的型別。decltype 用於獲取 cmp 的型別,以便在建立優先佇列時使用。

  2. 為何return m.second > n.second表示小根堆
    STL的比較器預設實現是std::less<T>,即等價為return a<b,其在sort中體現為元素從小到大排序,但在堆中卻對應著堆頂最大的大根堆。這是堆的底層實現導致的:

    priority_queue 的堆構建過程依賴於比較函式來決定哪個元素應該在堆的頂部。預設的比較是 std::less,即 a < b。那麼:當 a < b 時,意味著 b 比 a 更大,所以在堆的構建過程中,b 應該排在 a 的上面,讓較大的元素更靠近堆頂。因此,堆頂的元素是最大的,構成了一個大頂堆。

    因此,return m.second > n.second自然是n排在m的上面,形成小根堆

    以上內容是詢問GPT所得,方便理解,具體STL原始碼本人還未查閱,希望各位批評指正

2. 類

    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
  1. 為什麼不需要decltype
    使用類的方式時,編譯器已經知道這個類的型別,因此不需要使用 decltype。

思路

  1. 用map統計並儲存數字和頻率關係
  2. 定義小根堆,基本元素同樣是數字和頻率的鍵值對pair
  3. 遍歷map,堆中元素不足k個時直接入堆,否則判斷當前pair的頻次是否大於堆頂,大於則堆頂彈出,當前pair入堆;小於則不做處理
  4. 輸出堆中所有pair的key值-數字

程式碼

class Solution {
public:
    static bool cmp(pair<int,int>& m,pair<int,int>& n){
        return m.second > n.second; //堆的實現就是n更小於是讓n作為父節點——維護最小堆
    }
    vector<int> topKFrequent(vector<int>& nums, int k) {
        //統計元素出現頻率
        unordered_map<int,int> occur;
        for(auto& v : nums){
            occur[v]++;
        }
        //建立小根堆——只能暫時死記了
        priority_queue<pair<int,int>,vector<pair<int,int>>,decltype(&cmp)> q(cmp);  

        for(auto& [num,count] : occur){
            if(q.size() == k){
                if(q.top().second < count){
                    q.pop();
                    q.emplace(num,count);
                }
            }else{
                q.emplace(num,count);
            }
        }
        vector<int> ans;
        while(!q.empty()){
            ans.push_back(q.top().first);
            q.pop();
        }
        return ans;
    }
};

emplace_back 和push_back

這裡使用q.push(num,count)報錯

push:對於像std::priority_queue這種容器介面卡(底層通常是堆結構),push操作主要用於向容器中新增一個新元素。當使用push新增元素時,需要先構造好要新增的元素,然後將其複製(或移動)到容器內部的儲存結構中

emplace:emplace是 C++ 11 引入的一個函式,它的主要作用是在容器中直接原位構造(in - place construction)一個新元素,而不是先構造然後再複製(或移動)。這在效能上可能會有優勢,特別是當元素的建構函式比較複雜或者涉及資源分配時。

因此如要使用push則應改為q.push({num,count});

相關文章