题目描述:
给你一个整数数组 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 是数组大小。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/top-k-frequent-elements
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析:
题目要求时间复杂度 优于 O(n log n),是很棘手的一个事情,因为无论堆排序还是快排,时间复杂度都是O(n log n),没法更加优化了。常规思路,先遍历一遍nums,把数字i和对应出现的次数sum存入hash表map。但是如何在小于O(n log n)的时间复杂度里取出hash表中前k个大的元素,快排为O(n log n),只能是堆排序了,维持一个大小为k的小顶堆,然后取出hash表中前k个大的元素,时间复杂度为O(n log k)。
关键是如何建堆,之前的堆是只有一个元素,因此使用:
priority_queue<int,vector<int>,greater<int>> q;
就可以建立一个小顶堆,但是现在是要在堆中存储一对元素<num,sum>。这部分涉及到我的知识盲区了,只能去看评论和题解了。
经过查看评论和题解,以及查阅资料,掌握了存储一对元素的堆的建立方法:
priority_queue<pair<int,int> ,vector<pair<int,int>>,greater< pair<int,int> > > q;
其中,greater<pair<int,int>>,回先比较pair的第一个元素,然后比较第二个元素。
代码如下:
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 定义hash表存储出现的数字i和对应的出现次数
unordered_map<int,int> map;
for(int num:nums)
map[num]++;
// 定义pair<int,int>类型的小顶堆
// 其中greater< pair<int,int> >会在first相等时比较second
priority_queue<pair<int,int> ,vector<pair<int,int>>,greater< pair<int,int> > > q;
// 遍历map,维护一个大小为k的小顶堆
for(auto it:map)
{
// 堆中元素数量大于k时,替换头元素,重新排序堆
if(q.size()==k)
{
if(it.second>q.top().first)
{
q.pop();
// 以出现次数作为小顶堆的值插入
q.push(make_pair(it.second,it.first));
}
}
// 小于k直接插入
else
q.push(make_pair(it.second,it.first));
}
// 堆中剩余的就是出现频率前k高的元素,加入res数组
vector<int> res;
while(!q.empty())
{
res.push_back(q.top().second);
q.pop();
}
return res;
}
};
然后发现这道题可以用手写的堆,感觉比使用STL里面的要简单。。。。
代码如下:
class Solution {
public:
// 用数组heap模拟小顶堆
vector<pair<int,int>> heap;
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> map;
for(int num:nums)
map[num]++;
for(auto it:map)
{
// 堆中元素为k个时,如果要插入的元素更大,则替换堆顶,重新建堆
if(heap.size()==k)
{
if(heap[0].first<it.second)
{
heap[0]=make_pair(it.second,it.first);
adjust_heap(0);
}
}
// 堆中元素小于k个时,直接插入
else
push(make_pair(it.first,it.second));
}
vector<int> res;
while(heap.size()>0)
{
// 堆中剩余的k个元素,就是出现频率最高的k个数字
res.push_back(heap[0].second);
pop();
}
return res;
}
// 堆的删除操作
void pop()
{
// 交换堆头和堆尾的节点
swap(heap[0],heap[heap.size()-1]);
// 并删除交换后的堆尾节点
heap.erase(heap.end()-1);
// 重新建堆
adjust_heap(0);
}
// 堆的插入操作
void push(pair<int,int> p)
{
// 在堆的尾部插入节点
heap.push_back(make_pair(p.second,p.first));
// 调整堆为小顶堆
for(int i=(heap.size()-2)/2;i>=0;i--)
adjust_heap(i);
return;
}
// 调整堆
void adjust_heap(int index)
{
// 将第index个节点,及其子树,调整成为小顶堆
int left=index*2+1;
int right=index*2+2;
int min=index;
int len=heap.size();
// 比较左右节点的大小,选出新的堆顶元素
if(left<len&&heap[left].first<heap[min].first)
min=left;
if(right<len&&heap[right].first<heap[min].first)
min=right;
if(min!=index)
{
swap(heap[min],heap[index]);
// 调整子树,使其也维持一个小顶堆
adjust_heap(min);
}
return;
}
};