代码随想录:前K个高频元素
代码随想录链接:https://programmercarl.com/0347.%E5%89%8DK%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.html#%E6%80%9D%E8%B7%AF
力扣链接:https://leetcode.cn/problems/top-k-frequent-elements/
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
- 输入: nums = [1,1,1,2,2,3], k = 2
- 输出: [1,2]
示例 2:
- 输入: nums = [1], k = 1
- 输出: [1]
提示:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 O ( n log n ) O(n \log n) O(nlogn) , n 是数组的大小。
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
- 你可以按任意顺序返回答案。
1、优先队列priority_queue的相关知识
priority_queue是不允许随机访问,只能访问队列首部的元素,也只能对首部元素进行出队。在优先队列中,会按照一定的方式对队列中的元素进行排列。
这是priority_queue类的模板参数列表,里面有三个参数:class T,class Container = vector,class Compare = less
template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> >
class T:T是优先队列中存储的元素的类型,可以是int、double,也或者自定义的数据类型struct、class等;
class Container = vector<T>:Container是优先队列底层使用的存储结构,默认采用vector;
class Compare = less<typename Container::value_type>:Compar是定义优先队列中元素的比较方式的类,即如何对元素进行排列。
对元素排列的方式,可以是大顶堆(从大到小排列),或者小顶堆(从小到大排列),如不指明,则默认为大顶堆。
less<储存的数据类型> 即使用大顶堆
greater<储存的数据类型> 即是用小顶堆
priority_queue的定义和使用
priority_queue<Typename, Container<Typename>, Compare> queue_name;
priority_queue<int> q;//储存int型数据,容器默认为vector,比较方式默认为大顶堆
priority_queue<string> q;//储存string型数据,容器默认为vector,比较方式默认为大顶堆
priority_queue<int,vector<int>,less> q;//使用大顶堆储存int型数据
priority_queue<int,vector<int>,greater> q;//使用小顶堆储存int型数据
2、class Compare的实现
class Compare就是定义了一个类,在这个类中定义了一个成员函数,这个成员函数重载了’()'运算符,该成员函数的函数名为operator(),这里的()代表了被重载的运算符。这种类的定义,也叫做仿函数。仿函数就是假函数,它是把对象当作函数使用,所以也称为函数对象。因为普通函数在某些特殊场景下使用比较麻烦,所以就诞生了仿函数。
例如,大顶堆的less类的定义如下
template <class T>
struct less : binary_function <T,T,bool> {
bool operator() (const T& x, const T& y) const {return x<y;}
};
当数据类型为int型时,可以转化为如下代码:
class Less {
public:
bool operator()(const int a,const int b) const{
return a < b;
}
};
此时创建一个class Less的对象,并调用函数名为‘operator()’的成员函数,
Less lessCompare; //创建对象
lessCompare.operator()(2,8); //原本的调用格式,但是在C++中会省略'.operator()',使用简化的方式进行调用,
lessCompare(2,8);
这情况下,就实现了把对象当作函数使用,它其实就是因为C++重载运算符时,对象调用成员函数省略引用符号’.'和成员函数名‘operator()’造成的,实际上的代码逻辑仍然是对象调用成员函数。
priority_queue的class Compare比较类是使用仿函数来实现的,当我们自定义比较类时,也需要按照这种格式。因此,小顶堆类的定义如下:
// 小顶堆
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
在本题中,需要找到出现频率最高的数字,而数字和频率在哈希表unordered_map中成对存放,比较项是pair<int,int>中的第二项,且是小顶堆,因此左孩子的second值应为更小值,此时返回true。
此时小顶堆的定义如下:
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
参数的类型为pair<int,int>,容器底层使用vector,比较类为自定义的小顶堆mycomparison。
3、代码逻辑
使用unordered_map来统计数组中数字出现的频率,其中key为数值,value是出现的频率:
unordered_map<int, int> map;
for(int i : nums){
map[i]++;
}
用优先队列定义一个小顶堆pri_que,并使用小顶堆pri_que来存储map中的pair对,并且判断小顶堆的size是否超过了k,因为题目中要求需要计算的是前k的频率,如果超出k,就弹出前面的pair对,保证pri_que中只有k个元素。
priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> pri_que;
//用固定大小为k的小顶堆去扫描map,将频率前k的元素存储到pri_que中
for(auto it = map.begin(); it != map.end();it++){
pri_que.push(*it);
//判断pri_que的个数是否超出k
if(pri_que.size() > k){
pri_que.pop(); //弹出front,因为是从小到大排列,所以弹出了较小的频率
}
}
题目要求的返回前K个高频元素,类型为vector,并且因为小顶堆先弹出的是最小的,所以倒序来输出到固定大小为k的数组
vector<int> result(k);
for(int i = k-1; i >= 0; i--){
result[i] = pri_que.top().first;
pri_que.pop();
}
最终返回result即可。