347. 前 K 个高频元素 ●●
描述
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
题解
1. 哈希表+二分(效率低)
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> hashmap_num; // 哈希表记录元素个数
unordered_map<int, int> hashmap_index; // 哈希表记录元素在ans数组出现的位置
vector<int> ans;
for(int i : nums){
++hashmap_num[i];
if(hashmap_num[i] == 1){
ans.emplace_back(i); // 插入首次出现的元素
hashmap_index[i] = ans.size()-1;
}
else{
int l = 0;
int r = ans.size()-1;
while(l <= r){ // 二分法找到边界位置,注意判断l<=r
int mid = l + (r-l)/2; // 注意下面判断 ans[mid] != i
if(hashmap_num[ans[mid]] > hashmap_num[i]-1 && ans[mid] != i){
l = mid + 1; // l 第一个小于或等于
}else{
r = mid - 1; // r 最后一个大于
}
}
swap(ans[l], ans[hashmap_index[i]]); // 进行位置交换
swap(hashmap_index[ans[hashmap_index[i]]],hashmap_index[i]); // 索引交换
}
}
ans.resize(k);
return ans;
}
};
2. 哈希表
先统计出现最多的次数n
,循环减小 n 判断哈希表中 map.second = n
的元素,直到填满 k 个。
- 时间复杂度: O ( n ∗ N ) O(n*N) O(n∗N),n为最高频次,N为不同元素值个数
- 空间复杂度: O ( N ) O(N) O(N)
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// first对应元素value second对应元素出现次数
unordered_map<int, int>hashmap;
vector<int> ret;
// 记录元素出现次数
for(int i : nums) hashmap[i]++;
// 找到元素出现的最高频率次数
int maxtimes = 0;
for (auto i : hashmap) {
if (i.second > maxtimes) {
maxtimes = i.second;
}
}
// 从最高往低走 依次输出
while (k > 0){
for (auto i : hashmap){
if (i.second == maxtimes) {
ret.push_back(i.first);
k--;
}
}
maxtimes--;
}
return ret;
}
};
3. 频次 堆排序
建立并维护大小为 k 的小根堆。
总体时间复杂度 O ( n l o g k ) O(nlogk) O(nlogk):
- 遍历统计元素出现频率 O(n)
- 前 k 个数构造 规模为 k 的最小堆 minheap, O(k)。
- 遍历规模 k 之外的数据,大于堆顶则入堆,下沉维护规模为 k 的最小堆 minheap. O(nlogk)
(如需按频率输出,对规模为 k 的堆进行排序)
手写堆数据结构:
class Solution {
public:
void adjustHeap(vector<pair<int, int>>& heap, int idx, int k){ // 调整成为小顶堆
int child = 2 * idx + 1;
while(child < k){
if(child + 1 < k && heap[child+1].second < heap[child].second){
++child; // 选择频次更小的子节点
}
if(heap[idx].second > heap[child].second){ // 调整位置
swap(heap[idx], heap[child]);
idx = child;
child = 2*idx+1; // 更新比较节点
}else{
break;
}
}
}
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> hashmap;
for(int num : nums) ++hashmap[num]; // 统计频次
vector<pair<int, int>> heap(k); // 维护大小为k的小顶堆
int idx = 0;
for(std::unordered_map<int, int>::iterator iter = hashmap.begin(); iter != hashmap.end(); ++iter){
if(idx < k){
heap[idx++] = *iter; // 先将前k个元素建立堆
}else{
break;
}
}
for(int i = 2*k-1; i >= 0; --i){
adjustHeap(heap, i, k); // 从最后一个非叶子结点开始向上调整堆结构
}
idx = 0;
for(std::unordered_map<int, int>::iterator iter = hashmap.begin(); iter != hashmap.end(); ++iter){
if(idx < k){
++idx;
}else{
if(iter->second > heap[0].second){ // 对后续的节点进行比较更新堆结构
heap[0] = *iter;
adjustHeap(heap, 0, k);
}
}
}
vector<int> ans;
for(auto p : heap) ans.emplace_back(p.first); // 取出堆的内容
return ans;
}
};
- 使用优先队列容器
priority_queue
:
在优先队列中,优先级高的元素先出队列,并非按照先进先出的要求,类似一个堆(heap)。
priority_queue<Type, Container, Functional>;
其中Type为数据类型,Container为保存数据的容器,Functional为元素比较方式。
如果使用了第三个参数,那第二个参数不能省。
Container必须是用数组实现的容器,比如 vector, deque。
STL里面默认用的是vector,比较方式默认用 operator < 从小到大,即所以如果把后面两个参数缺省的话,优先队列就是大顶堆,队头元素最大。
使用 greater<int>
后,数据从大到小排列,堆结构对应为小顶堆,top()返回的就是最小值而不是最大值!
class Solution {
public:
// 小顶堆,比较函数
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second; // 用小顶堆首位交换排序的结果则是 从大到小,因此 >
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 要统计元素出现频率
unordered_map<int, int> map; // map<nums[i],对应出现的次数>
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 对频率排序
// 定义一个小顶堆,大小为k, priority_queue<Type, Container, Functional>
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
// 用固定大小为k的小顶堆,扫面所有频率的数值
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
pri_que.pop();
}
}
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> result(k);
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};
4. 频次 桶排序
统计最大频次,建立大小为 maxCnt 的桶集合。
// 桶排序(根据频率排序)
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> countMap;
int maxCount = 0;
for (const int& x : nums) {
maxCount = std::max(maxCount, ++countMap[x]); // 利用map统计每个元素的频率,同时得出最大频率
}
vector<vector<int>> buckets(maxCount + 1); // 桶的大小为maxCount+1 (即同一个桶中的元素频率相同)
for (const auto& pair : countMap) {
buckets[pair.second].push_back(pair.first); // 按照map中记录的每个元素频率 将元素放入相应桶中
}
vector<int> result;
for (int i = maxCount; i >= 0 && result.size() < k; --i) { // 从 最后一个桶(频率最高) 往前遍历,从而得出前k个高频元素
for (const auto& x : buckets[i]) {
result.push_back(x);
if (result.size() == k) break;
}
}
return result;
}
};