法一、用数组排序
思路
-
用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});