给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例2:
输入: nums = [1], k = 1
输出: [1]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/top-k-frequent-elements
在前面的文章已经介绍过优先队列解题的思路,这篇文章对优先队列的介绍很详细。这道题依然可以使用一个存储pair
数据的优先队列priority_queue
来解题。此时pair
中的k-v
关系应当是:频率-元素值
;因为默认的优先队列是一个大顶堆,所以我们直接使用它的话就选择将数组内所有的频率-元素值
对存储到优先队列中,此时会根据频率进行自动的排序,到所有的pair元素插入队列之后,我们输出k次堆顶元素(也就是队首元素),就完成了最高频的前k个元素。
对于优先队列所需要存储的频率-元素值对的求法,可以通过对数组进行排序,然后在再次遍历数组的时候累计求得。具体代码如3.1所示。
前面直接使用大顶堆的话,虽然时间复杂度上较为可以,但是我们发现,需要存储所有元素的频率-元素值对,这就造成很大的空间浪费。既然题目是要求我们求解前k
个高频元素,那么其实我们每次只维护k
个元素的队列就可以了。我们可以设想出这样的流程
在一个空间维护
k
个元素,每当有一个新的元素P
需要加入的时候,我们就看这个空间的最小的元素min
是否大于这个待加入元素。如果min>P
,则说明P
不是前k
个最大的元素;否则的话,p>min
,min
就应该被淘汰。
但这样的话是无法靠大顶堆实现的。因为大顶堆的堆顶是最大元素,而且我们无法简单的查找出当前堆哪个元素最小。与大顶堆对应的一个数据结构——小顶堆,则可以实现这样的操作。于是我们试图用小顶堆来进行优先队列的初始化。
既然是求前k个值,那么我们可以想到快排中的partition算法就是根据基准元素将数组划分为两个部分,基准元素前面和后面的分别可以是大于基准元素和小于基准元素的值。这就很契合我们这道题了。快排的具体实现可以看这篇文章。
在这道题运用快排基准划分的方法的话,我们需要将map
保存的键值对存储到vector
进行处理,因为vector
可以进行高效的基于下标的交换元素。但是由于各个数据结构的特性,在元素值-频率
键值对存储的时候还是要用到map
结构。
所以我们的实现代码就如3.3所示,代码注释解释了解题思路。
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
int len = nums.size();
sort(nums.begin(),nums.end());
priority_queue<pair<int,int>> que;
for(int i=0;i<len;i++){
int p=1;
while(i+1<len && nums[i]==nums[i+1]){
p++;
i++;
}
que.emplace(p,nums[i]);
}
vector<int> res;
while(k--){
res.push_back(que.top().second);
que.pop();
}
return res;
}
};
class Solution {
public:
//定义比较顺序
struct cmp{
public:
bool operator()(const pair<int,int> &p1,const pair<int,int> &p2){
return p1.second>p2.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 统计元素值和频率映射
unordered_map<int,int> occurences;
for(auto c:nums){
occurences[c]++;
}
// 小顶堆的定义
priority_queue<pair<int,int>,vector<pair<int,int>>,cmp> pri_que;
//根据map内元素键值对进行小顶堆的数据保存
for(unordered_map<int,int>::iterator iter=occurences.begin();iter!=occurences.end();++iter){
pri_que.push(*iter);
if(pri_que.size()>k){
pri_que.pop();
}
}
vector<int> res;
//保存k个元素
while(k--){
res.push_back(pri_que.top().first);
pri_que.pop();
}
return res;
}
};
class Solution {
public:
void quick_sort(vector<pair<int,int>> &vp,int start,int end,int k,vector<int>& res){
//随机确定基准元素
int picked = rand()%(end-start+1)+start;
swap(vp[start],vp[picked]);
int pivot = vp[start].second;
//同向双指针快排partition函数
int index = start;
for(int i=start+1;i<=end;i++){
if(vp[i].second>=pivot){
index++;
swap(vp[i],vp[index]);
}
}
swap(vp[start],vp[index]);
//如果基准元素及其左边的元素个数大于k个,
//说明找多了,则在左边缩小范围继续查找
if(k<index-start+1){
quick_sort(vp,start,index-1,k,res);
}else{
//否则就是找少了,则先将找到的最大个index-start+1个元素进行保存
// 再找剩下的k-(index-start+1)个元素
for(int i=start;i<=index;i++){
res.push_back(vp[i].first);
}
if(k>index-start+1){
quick_sort(vp,index+1,end,k-(index-start+1),res);
}
}
}
vector<int> topKFrequent(vector<int>& nums, int k) {
// 保存元素频率映射
unordered_map<int,int> m;
for (auto& v : nums) {
m[v]++;
}
//map不方便进行排序,采用vector辅助操作
vector<pair<int,int>> pm;
for(auto &pa:m){
pm.push_back(pa);
}
vector<int> res;
quick_sort(pm,0,pm.size()-1,k,res);
return res;
}
};
总结:数据结构——堆