给你一个整数数组
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
是数组大小。
思路:
- 要统计元素出现次数(通过map:cnt)
- 对次数排序(通过优先级队列:pq)
- 找出前K个高频元素
时间复杂度:O(nlogk),n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);接着,遍历用于存储元素频率的 map,如果元素的频率大于大顶堆中顶部的元素,则将顶部的元素删除并将该元素加入堆中,这里维护堆的数目是 k,所以这一系列操作的时间复杂度是 O(nlogk) 的;因此,总的时间复杂度是 O(nlogk)。
空间复杂度: O(n),最坏情况下(每个元素都不同),map 需要存储 n 个键值对,优先队列需要存储 k 个元素,因此,空间复杂度是 O(n)。
vector<int> topKFrequent(vector<int>& nums, int k) {
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; // 优先级队列(大顶堆)
unordered_map<int, int> cnt; // 负责统计每个元素出现的次数 key:元素值 value:元素出现次数
// for (auto num : nums) cnt[num]++;
for (int i = 0; i < nums.size(); i++)
{
cnt[nums[i]] += 1;
}
// for (auto kv : cnt) {
// pq.push({kv.second, kv.first});
// while (pq.size() > k) pq.pop();
// }
for (unordered_map<int, int>::iterator it = cnt.begin(); it != cnt.end(); it++)
{
pq.push({it->second, it->first}); // 根据map中的key(次数)来排序,value为次数对应的元素值
if (pq.size() > k) // pq始终维护长度为k的队列(此处提前限定了pq长度为k,下面while不用for (int i = 0; i < k; i++) 了)
{
pq.pop();
}
}
// vector<int> res;
// while (!pq.empty())
// {
// res.push_back(pq.top().second);
// pq.pop();
// }
// return res;
vector<int> res;
while (!pq.empty())
{
res.push_back(pq.top().second); // 从队首依次从大到小获取每个元素的second域(元素值),并存入v
pq.pop();
}
return res;
}
这道题做的快自闭了,STL的优先级队列刚开始自己写语法都有问题 = = !哎难受~
补充一波Go实现的解法,不过可能时间复杂度不满足题目要求的优于 O(n log n) ,因为排序过程也有自己的时间复杂度,且算是一种方法吧 - - !
func topKFrequent(nums []int, k int) []int {
// 1.利用map统计每个元素出现频次
m := make(map[int]int)
for _, val := range nums {
m[val] += 1
}
// 2.将map转为结构体数组,再利用sort包按结构体的Val"从大到小"排序
type KV struct {
Key int
Val int
}
kvSlice := make([]KV, 0)
for k, v := range m {
kv := KV{k, v}
kvSlice = append(kvSlice, kv)
}
sort.Slice(kvSlice, func(i, j int) bool { // 对结构体的val值"从大到小"排序
return kvSlice[i].Val > kvSlice[j].Val
})
fmt.Println(kvSlice)
// 3.输出前k个高频元素
res := make([]int, 0)
for i := 0; i < k; i++ {
res = append(res, kvSlice[i].Key)
}
return res
}
// 上述过程简化版:
func topKFrequent(nums []int, k int) []int {
m := make(map[int]int, 0)
for i := 0; i < len(nums); i++ {
m[nums[i]] += 1
}
// 去重后的nums数组
res := make([]int, 0)
for k, _ := range m {
res = append(res, k)
}
// 根据【出现频次】对【去重后的nums数组】排序
sort.Slice(res, func(i, j int) bool {
return m[res[i]] > m[res[j]]
})
return res[:k]
}
// 另一种方法 堆排序:
// 建立大小为k的 小顶堆,将map统计的频次依次加入堆中。后续如果出现频次更高的元素,那就替换掉小顶堆堆顶元素即可~
// 时间复杂度:O(nlogk)
// 那为什么不能用大顶堆呢?
// 你想啊,如果定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。