[LeetCode]day34 347.前k个高频元素
题目链接
题目描述
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
题解
思路
要求频率前k个高的元素的元素,很自然的就是先想到用map来收集每个元素出现的频率,然后对这些频率进行排序
所以这道题的难点就转化成了,怎么对频率进行排序?
这道题考察的是优先级队列的知识点
什么是优先级队列?
队列进出的顺序按照优先级,而不是进入的前后顺序
举个例子: 医院中处理病人不是按照病人来的顺序,而是根据病情的严重程度
我们需要找到频率前k高的元素,可以选择维护一个大小为k的堆,使堆里面的元素总是前k个大的。
那么要使用小顶堆还是大顶堆呢?
在小顶堆中,每次弹出的元素都是堆中最小的元素,堆中剩的是大的;大顶堆中,每次弹出的元素都是堆中最大的元素,堆中剩的是小的。
所以我们要选择小顶堆。
c++中恰好提供了这样一种队列,即priority_queue,队列内部的元素总是有序的,按照元素的权值进行排序。
所以我们可以设置一个大小为k的优先队列,遍历Map的过程中去维护这个队列。最后队列里面剩下的就是前k个出现频率最高的元素啦
代码书写
class MyComparison{
public:
bool operator()(const pair<int,int>&m,const pair<int,int>&n){
return m.second>n.second;
}
};
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int>myMap;
vector<int>re(k);
for(int i =0;i<nums.size();i++){
myMap[nums[i]]++;
}
priority_queue<pair<int,int>,vector<pair<int,int>>,MyComparison>q;
for(auto it = myMap.begin();it!=myMap.end();it++){
q.push(*it);
if(q.size()>k){
q.pop();
}
}
for(int i=0;i<k;i++){
re[i]=q.top().first;
q.pop();
}
return re;
}
};
可能会有像我一样的c++苦手看得眼花缭乱
我现在可以详细的解释一下。
-
为什么采用
unordered_map
去收集每个元素的频率?
因为我们不需要使键是有顺序的,并且unorder_map底层逻辑实现是哈希表,在平均情况下查找效率为O(1) 效率更高 -
priority_queue<pair<int,int>,vector<pair<int,int>>,MyComparison>q;
是什么意思?
第一个模版参数pair<int,int>
代表这个优先级队列中存放的数据是pair<int,int>类型;
第二个模版参数vector<pair<int,int>>
代表这个队列的底层容器是vector<pair<int,int>>
第三个模版参数MyComparison
是一个自定义的类,为什么这里可以直接放一个类呢?
priority_queque的声明如下:
template<
class T, // 元素类型
class Container = vector<T>, // 底层容器(默认vector)
class Compare = less<T> // 比较器类型(默认less<T>)
> class priority_queue;
第三个参数 Compare 必须是一个类型,而不是函数或对象。
例如,less 和 greater 是STL中预定义的类型,它们内部重载了 operator(),因此可以生成函数对象(Functor)
- 为什么要重新写一个类重载()呢?
默认比较器会按 pair 的第一个元素的大小进行排序,而我们需要按第二个元素的大小进行排序,所以需要重写。