法一、用陣列排序
思路
-
用map儲存元素和頻率關係
-
將元素和頻率的鍵值對pair作為vector的基本元素,以頻率為準進行從大到小的排序 —— O(nlogn)
-
輸出前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);
-
priority_queue
這裡建立了一個優先佇列 q,其元素型別為pair<int, int>
。
vector<pair<int, int>>
是用來儲存優先佇列元素的底層容器。
decltype(&cmp) 是一個型別推導
,表示 cmp 函式的型別。decltype 用於獲取 cmp 的型別,以便在建立優先佇列時使用。 -
為何
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;
- 為什麼不需要decltype
使用類的方式時,編譯器已經知道這個類的型別,因此不需要使用 decltype。
思路
- 用map統計並儲存數字和頻率關係
- 定義小根堆,基本元素同樣是數字和頻率的鍵值對pair
- 遍歷map,堆中元素不足k個時直接入堆,否則判斷當前pair的頻次是否大於堆頂,大於則堆頂彈出,當前pair入堆;小於則不做處理
- 輸出堆中所有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});