一 题目
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
二 前言
像这种topK问题(前k小/前k大/第k大/第k小)的题解都是套路,基本就是堆排序O(nlogk)、快速选择O(n)【最快】、二叉搜索树O(nlogk)。本题只是在这基础上的变形而已,要处理值和频率对应的问题,所以考虑以哈希表为底层的unordered_map来处理,然后在后面排序的过程中需要注意数据结构的变化。
三 题解
1.两个红黑树反着赋值。
思路:考虑到c++中的map底层是红黑树实现的,会对key值排序,其遍历结果默认为从小到大,因此可以选择用map实现,利用map对频率进行排序,则最后反向迭代器遍历出的前k个数即为题目所求。myMap1用来统计各个数字出现的频率,再反向赋值给myMap2,因为频率相同的数字可能会重复,所以要用容器来装,然后用反向迭代器遍历结果。
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int,int>myMap1;
//因为频率相同的数字可能不止一个,所以要用数组来存
map<int,vector<int>>myMap2;
// 数字对应频率映射
for(int n : nums){
myMap1[n]++;
}
// 频率对应数字映射
for(auto it = myMap1.begin(); it!= myMap1.end(); it++){
myMap2[it->second].push_back(it->first);
}
vector<int>ans;
//考虑到myMap2的迭代器可能指向一个数组,所以不能用下标值的方式去给ans赋值
for(auto it = myMap2.rbegin(); it != myMap2.rend(); it++){
ans.insert(ans.end(), (it->second).begin(), (it->second).end());
if(ans.size() == k)
break;
}
return ans;
}
};
2.堆
思路:与普通的topK类型题目一致,可以使用小根堆来维护前k个出现频率大的数,而在此基础上要用哈希表来处理数值与频率的对应关系。哈希表处理完映射关系以后,遍历该表,当堆中元素小于k时直接插入,当剩余k+1个元素则要判断其频率和堆顶的大小关系,如果遍历到的元素出现频率比堆顶大,堆顶就应该被弹出,否则就跳过(←所以要写成两个if语句,不能合并写。
class Solution {
public:
class cmp{
public:
bool operator()(pair<int,int>&a,pair<int,int>&b){
return a.second > b.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int>HSTable;
for(int n : nums){
HSTable[n]++;
}
priority_queue<pair<int,int>,vector<pair<int,int>>,cmp>Q;
for(auto [num,counts] : HSTable){
if(Q.size() >= k){
if(counts > Q.top().second){//不能合并成一个if语句
Q.pop();
Q.emplace(num,counts);
}
}else
Q.emplace(num,counts);
}
vector<int>ans;
while(!Q.empty()){
ans.emplace_back(Q.top().first);
Q.pop();
}
return ans;
}
};
注:
1.emplace、emplace_back、emplace_front分别对应insert、push_back、push_front,是C++11增加的新特性,相比push_back更为优化,不用移动或拷贝内存。
2.有关增强型循环的理解:
for(auto a : b) 用a遍历容器b获取其每一个值,但是不能通过修改a来修改b容器中的元素。
for(auto &a : b)传引用的方式,使得可以通过a对容器b内的元素进行更改。
3.重载 < 运算符
C++中的sort()是单调递增的,也就是本来是 < ,但如果把小于号重载成大于号,那么元素就是单调递减了,而c++中的堆就相当于sort数组完以后,把最后一个元素当作堆顶。因此重载成大于号以后,本来C++默认底层是大根堆的优先队列就会变成小根堆。
4.仿函数
仿函数是使一个类的使用看上去像一个函数,实际上就是在类中实现了operator(),使这个类具有类似函数的行为,所以是仿函数类。而我们通常写的greater<int>或者less<int>也是仿函数。
关于自定义排序的问题可以具体看:C++中的仿函数functor_我的大学-CSDN博客_c++仿函数
3.快速选择
思路也差不多,频率用哈希表处理,分治地选择其中一边来快排。想要实现升序或降序只用更改partition()里两个while循环对cur[r].second和pivot之间大于或小于的关系。
class Solution {
public:
void randomParttition(vector<pair<int,int> >& cur, int l, int r){
int pivot = rand() % (r-l+1) + l;
swap(cur[l],cur[pivot]);
}
int partition(vector<pair<int,int> >& cur, int l, int r){
randomParttition(cur,l, r);
int pivot = cur[l].second;
int value = cur[l].first;
while(l < r){
while(l < r && cur[r].second <= pivot) --r;
cur[l] = cur[r];
while(l < r && cur[l].second >= pivot) ++l;
cur[r] = cur[l];
}
cur[l].first = value;
cur[l].second = pivot;
return l;
}
void quickSort(vector<pair<int,int> >& cur, int l, int r, int k){
if(l >= r) return;
int pivot = partition(cur,l,r);
if(pivot == k) return;
if(pivot < k)
quickSort(cur,pivot+1,r,k);
if(pivot > k)
quickSort(cur,l,pivot-1,k);
}
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int>HSTable;
// 哈希表存入数字对应频率的映射
for(int i : nums){
HSTable[i]++;
}
vector<pair<int,int> >cur;
// 初始化待快排的数组
for(auto j : HSTable){
cur.emplace_back(j);
}
quickSort(cur,0,cur.size()-1,k-1);
vector<int>ans(k);
for(int i = 0; i < k; i++){
ans[i] = cur[i].first;
}
return ans;
}
};
4.二叉搜索树
思路基本跟小根堆那个做法是一样的,先用哈希表统计频率,然后建造一个大小为k的二叉搜索树来维护前k个频率大的数。