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
个高频元素的集合是唯一的
**进阶:**你所设计算法的时间复杂度 必须 优于 O(n log n)
,其中 n
是数组大小。
二、解法
方法一:哈希表+数组
算法思路:
本题的关键是要找到数组中出现频率最高的K个元素。一个直观的思路是使用哈希表记录每个元素出现的次数,然后对记录的次数进行排序,取出次数最多的K个元素即可。
具体步骤是:
- 遍历数组,使用哈希表记录每个元素出现的次数
- 将记录的元素次数节点放入vector中
- 对vector进行排序,排序规则是按出现次数降序
- 从排序后的vector取出前K个元素,这就是TOP K个高频元素
具体实现:
- 使用哈希表nums_map记录每个元素出现的次数
- 遍历数组,统计每个元素出现的次数
- 将哈希表中的记录节点转化为pair对,存入vec向量中
- 利用sort对vec进行排序,排序规则通过compare函数指定为对第二个元素降序排序
- 从vec中取出前k个pair,并将first元素放入result中
这里的关键点是compare函数指定了排序规则,并且sort后的vec并不是我们直接要的结果,而是需要从中提取出top k的first元素。
class Solution {
public:
static bool compare(const pair<int,int>& v1,const pair<int,int>& v2){
return v1.second > v2.second;
}
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int,int> nums_map;
vector<int> result;
vector<pair<int,int>> vec;
for(int i = 0; i < nums.size(); i++){
nums_map[nums[i]]++;
}
for(auto it = nums_map.begin(); it != nums_map.end(); it++){
vec.push_back(make_pair(it->first,it->second));
}
sort(vec.begin(), vec.end(), compare);
for(int i = 0; i < k; i++) {
result.push_back(vec[i].first);
}
return result;
}
};
算法分析:
- 时间复杂度为O(NlogN),遍历数组为O(N),排序为O(NlogN)
- 空间复杂度为O(N),哈希表存储为O(N),vec向量存储也为O(N)
方法二:优先队列
算法思路
这个算法的核心思路是利用哈希表统计元素频率,并维护一个大小为 K 的最小堆,final结果储存在最小堆里。具体步骤如下:
- 使用哈希表统计每个元素出现的频率
- 将统计结果以 pair 对的形式插入最小堆
- 堆的大小维持为 K
- 堆首元素弹出,代表频率最小的元素
- 最后最小堆里剩下的 K 个元素就是题目要求的 Top K 个高频元素
最小堆的排序规则通过自定义 compare 函数实现。
具体实现
- 使用 map 统计每个元素的频率
- 使用 priority_queue 声明一个最小堆,自定义比较函数 compare
- 遍历 map,用 pair 对封装元素和频率,插入到最小堆
- 堆的大小超过 K 时,弹出堆顶元素
- 结果数组通过最小堆的 top() 函数获取
class Solution {
public:
static bool compare(const pair<int,int>& v1,const pair<int,int>& v2){
return v1.second > v2.second;
}
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int,int> nums_map;
for(int i = 0; i < nums.size(); i++){
nums_map[nums[i]]++;
}
priority_queue<pair<int,int>, vector<pair<int,int>>, decltype(&compare)> pri_que(compare);//建立最小优先队列
for(map<int,int>::iterator it = nums_map.begin();it != nums_map.end();it++){
pri_que.push(*it);
if(pri_que.size() > k){
pri_que.pop();
}
}
//写入结果数组
vector<int> result(k);
for(int i = k-1; i >= 0; i--){
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};
算法分析
- 时间复杂度为 O(NlogK),统计频率 O(N),维护堆 O(NlogK)
- 空间复杂度为 O(N),map 存储频率 O(N),优先队列空间 O(K)
三、拓展(方法二里优先队列声明)
方法二里优先队列的声明是:
priority_queue<pair<int,int>, vector<pair<int,int>>, decltype(&compare)> pri_que(compare);
这个语句中:
priority_queue
表示声明一个优先队列<pair<int,int>, vector<pair<int,int>>
指定了优先队列的模板参数,第一个是元素类型,第二个是底层容器类型decltype(&compare)
是比较函数的类型>
表示模板参数列表结束pri_que
是优先队列的变量名(compare)
是以比较函数作为参数构造优先队列
而pri_que(compare)
则表示以 compare 函数为参数构造这个优先队列,将排序规则传入。