问题描述
给你一个整数数组 nums 和一个整数 k,请你用一个字符串返回其中出现频率前 k 高的元素。请按升序排列。
你所设计算法的时间复杂度必须优于 O(n log n),其中 n 是数组大小。
输入
- nums: 一个正整数数组
- k: 一个整数
返回
返回一个包含 k 个元素的字符串,数字元素之间用逗号分隔。数字元素按升序排列,表示出现频率最高的 k 个元素。
参数限制
- 1 <= nums[i] <= 10^4
- 1 <= nums.length <= 10^5
- k 的取值范围是 [1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
测试样例
样例1:
输入:
nums = [1, 1, 1, 2, 2, 3], k = 2
输出:"1,2"
解释:元素 1 出现了 3 次,元素 2 出现了 2 次,元素 3 出现了 1 次。因此前两个高频元素是 1 和 2。
样例2:
输入:
nums = [1], k = 1
输出:"1"
样例3:
输入:
nums = [4, 4, 4, 2, 2, 2, 3, 3, 1], k = 2
输出:"2,4"
题目分析
我选它是因为它是个“难”题还可以让我水一篇我们在这里讲一下一些不常规的优化方法。
这题其实挺简单:1.统计频率 2.对频率数组排序 3.取前k个结果进行输出
首先我们发现:
- 1 <= nums[i] <= 10^4
- 1 <= nums.length <= 10^5
那就有意思了,不同于其他比如说字符串数组,我们不需要构造map之类的容器,而是可以暴力开一个足够大的数组。这样,遍历原数组取得每个数字的频率时,可以在O(n)内完成。
随后,遍历 freq 数组,构造一个包含所有出现过的数字及其对应频率的pair数组 freq_nums。这里时间复杂度:O(U),其中U是不同元素的个数。在最坏情况下,U=10^4。
vector<pair<int, int>> freq_nums;
for (int num = 1; num <= 10000; ++num) {
if (freq[num] > 0) {
freq_nums.emplace_back(freq[num], num);
}
}
排序,时间复杂度O(UlogU),提取前k个数字,按数字大小排列,时间复杂度O(klogk)
sort(freq_nums.begin(), freq_nums.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
if (a.first != b.first)
return a.first > b.first;
else
return a.second < b.second;
});
vector<int> result_nums;
for (int i = 0; i < k; ++i) {
result_nums.push_back(freq_nums[i].second);
}
sort(result_nums.begin(), result_nums.end());
但是,最终时间复杂度不是O(UlogU),而可以压缩到O(Ulogk)。
为什么?因为我们只需要前k个频繁的数字,所以,我们可以使用最小堆priority_queue保留频率最高的k个元素,从而避免对所有元素排序。
最小堆是一颗完全二叉树,所有层都被填满,只有最后一层可以不完全填充,并且从左到右依次填充节点。堆中每个节点的值都小于或等于其子节点的值。因此,我们改用 unordered_map 统计每个数字的频率,遍历 unordered_map 中的每个元素,用最小堆维护前k个最高频率的元素。当堆的大小超过k时,移除频率最低的元素,即堆顶。
最终代码:
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unordered_map>
#include <sstream>
#include <algorithm>
using namespace std;
string solution(vector<int> nums, int k) {
unordered_map<int, int> freq;
for (int num : nums) {
freq[num]++;
}
auto comp = [](const pair<int, int>& a, const pair<int, int>& b) {
if (a.first != b.first)
return a.first > b.first;
return a.second < b.second;
};
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(comp)> min_heap(comp);
for (const auto& [num, count] : freq) {
min_heap.emplace(count, num);
if (min_heap.size() > k) {
min_heap.pop();
}
}
vector<int> result_nums;
while (!min_heap.empty()) {
result_nums.push_back(min_heap.top().second);
min_heap.pop();
}
sort(result_nums.begin(), result_nums.end());
ostringstream oss;
for (size_t i = 0; i < result_nums.size(); ++i) {
if (i > 0) oss << ",";
oss << result_nums[i];
}
return oss.str();
}
int main() {
// You can add more test cases here
std::vector<int> nums1 = {1, 1, 1, 2, 2, 3};
std::vector<int> nums2 = {1};
std::cout << (solution(nums1, 2) == "1,2") << std::endl;
std::cout << (solution(nums2, 1) == "1") << std::endl;
return 0;
}
本题结束。