leetcode:347. 前 K 个高频元素
题目
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。
示例:
输入: nums = [1,1,1,2,2,3], k = 2输出: [1,2]
输入: nums = [1], k = 1输出: [1]
解答 & 代码
解法一:哈希表 + 大根堆
class Solution {private:struct cmp {bool operator() (vector<int> nums1, vector<int> nums2) {return nums1[1] < nums2[1];}};public:vector<int> topKFrequent(vector<int>& nums, int k) {// 哈希表,存储数值出现次数,key = 数值,val = 出现次数unordered_map<int, int> numCntMap;for(int i = 0; i < nums.size(); ++i)++numCntMap[nums[i]];// 优先队列实现的大根堆,存储 vector<int>{数值,出现次数}, 并用出现次数来比较大小priority_queue<vector<int>, vector<vector<int>>, cmp> maxHeap;for(auto it = numCntMap.begin(); it != numCntMap.end(); ++it)maxHeap.push(vector<int>{it->first, it->second});// 大根堆依次弹出前 K 个元素,即为前 K 个高频元素vector<int> result;for(int i = 0; i < k; ++i){int num = maxHeap.top()[0];maxHeap.pop();result.push_back(num);}return result;}};
复杂度分析:设数组长度为 N,包含 M 种不同的数值
- 时间复杂度 O(M logM):
- 遍历数组,将数值及其出现次数存入哈希表,时间复杂度 O(N)
- 遍历哈希表,将 M 个键值存入优先队列,时间复杂度 O(MlogM)
- 从优先队列中弹出前 K 个元素,时间复杂度 O(KlogM)
- 因此,总的时间复杂度 O(M logM),满足题目要求的优于 O(N logN)
- 空间复杂度 O(M):
- 哈希表和优先队列的最大长度都是 M
- 结果数组长为 K,不计
执行结果:
执行结果:通过执行用时:36 ms, 在所有 C++ 提交中击败了 5.92% 的用户内存消耗:15.7 MB, 在所有 C++ 提交中击败了 5.03% 的用户
解法二:哈希表 + 小根堆
由于题目只要求返回前 K 个高频元素,而不要求这 K 个元素按频次排序,因此可以用小根堆代替大根堆,这样堆中只需要存储 K 个元素,而不用存储全部 M 个键值,每次插入、删除的时间复杂度从 O(logM) 降为 O(logK),最坏情况下 M 个键值对各插入、删除一次,总体时间复杂度从 O(M logM) 降为 O(M logK)
struct cmp {bool operator() (pair<int, int> pair1, pair<int, int> pair2){return pair1.second > pair2.second;}};class Solution {public:vector<int> topKFrequent(vector<int>& nums, int k) {// 哈希表统计每个元素出现的次数,key = 元素数值,val = 出现次数unordered_map<int, int> numFreqMap;for(int i = 0; i < nums.size(); ++i)++numFreqMap[nums[i]];// 优先队列(小根堆),维护前 k 个高频元素priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> minHeap;for(auto it = numFreqMap.begin(); it != numFreqMap.end(); ++it){// 堆的元素个数小于 k 时,直接插入if(minHeap.size() < k)minHeap.push(make_pair(it->first, it->second));// 如果堆的元素个数等于 k,则比较堆顶和当前元素的大小,// 如果当前元素更大,则弹出堆顶,插入当前元素;否则不操作,舍弃当前元素else{if(it->second > minHeap.top().second){minHeap.pop();minHeap.push(make_pair(it->first, it->second));}}}// 将堆中的 k 各元素弹出,存入结果数组并返回vector<int> result(k);for(int i = k - 1; i >= 0; --i){result[i] = minHeap.top().first;minHeap.pop();}return result;}};
复杂度分析:设数组长度为 N,包含 M 种不同的数值,题目要求返回前 K 个高频元素
- 时间复杂度 O(M logK):
- 遍历数组,将数值及其出现次数存入哈希表,时间复杂度 O(N)
- 优先队列中只会有 K 个元素,因此每次插入、删除操作时间复杂度 O(logK)。而最坏情况下哈希表中每个键值对都会插入优先队列一次,删除一次,因此时间复杂度 O(M logK)
- 因此,总的时间复杂度 O(M logK),满足题目要求的优于 O(N logN)
- 空间复杂度 O(M):
- 哈希表最大长度是 M
- 小根堆的最大长度是 K
- 结果数组长为 K,不计
执行结果:
执行结果:通过执行用时:28 ms, 在所有 C++ 提交中击败了 5.94% 的用户内存消耗:15.6 MB, 在所有 C++ 提交中击败了 5.01% 的用户
解法三:哈希表 + 快速选择
class Solution {private:// 快排分割函数int partition(vector<pair<int, int>>& arr, int left, int right){int randomIdx = random() % (right - left + 1) + left;swap(arr[left], arr[randomIdx]);pair<int, int> pivot = arr[left];while(left < right){while(left < right && arr[right].second <= pivot.second)--right;arr[left] = arr[right];while(left < right && arr[left].second >= pivot.second)++left;arr[right] = arr[left];}arr[left] = pivot;return left;}// 快速选择:使得数组第 k 大的元素在第 k 个位置上(arr[k-1]),且前面的数都比它大,后面的数都比它小void quickSelect(vector<pair<int, int>>& arr, int left, int right, int k){if(left >= right)return;int pivot = partition(arr, left, right);if(pivot == k - 1)return;else if(pivot < k - 1)quickSort(arr, pivot + 1, right, k);elsequickSort(arr, left, pivot - 1, k);}public:vector<int> topKFrequent(vector<int>& nums, int k) {// 哈希表,存储数值出现次数,key = 数值,val = 出现次数unordered_map<int, int> numCntMap;for(int i = 0; i < nums.size(); ++i)++numCntMap[nums[i]];// 将哈希表中的键值对存入数组 arr,并进行快速选择vector<pair<int, int>> arr;for(auto it = numCntMap.begin(); it != numCntMap.end(); ++it)arr.push_back(make_pair(it->first, it->second));quickSelect(arr, 0, arr.size() - 1, k);// 将 arr 的前 k 个元素存入结果数组,并返回vector<int> result(k);for(int i = 0; i < k; ++i)result[i] = arr[i].first;return result;}};
复杂度分析:设数组长度为 N,包含 M 种不同的数值,题目要求返回前 K 个高频元素
- 时间复杂度 O(N):
- 遍历数组
nums,将数值及其出现次数存入哈希表,时间复杂度 O(N) - 遍历哈希表,将哈希表中的键值对都存入数组
arr,时间复杂度 O(M) - 对数组
arr进行快速选择,平均时间复杂度 O(N) - 遍历快速选择后的数组
arr的前 k 个元素,存入结果数组,时间复杂度 O(K) - 因此,总的时间复杂度 O(N),满足题目要求的优于 O(N logN)
- 遍历数组
- 空间复杂度 O(M):
- 哈希表最大长度是 M
- 数组
arr长度 M - 结果数组长为 K,不计
执行结果:
执行结果:通过执行用时:12 ms, 在所有 C++ 提交中击败了 83.95% 的用户内存消耗:13.2 MB, 在所有 C++ 提交中击败了 86.69% 的用户
