0
点赞
收藏
分享

微信扫一扫

LeetCode-347-前k个高频元素-中等(红黑树/堆/快排)

敬亭阁主 2022-03-12 阅读 55

一 题目

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

二 前言

像这种topK问题(前k小/前k大/第k大/第k小)的题解都是套路,基本就是堆排序O(nlogk)、快速选择O(n)【最快】、二叉搜索树O(nlogk)。本题只是在这基础上的变形而已,要处理值和频率对应的问题,所以考虑以哈希表为底层的unordered_map来处理,然后在后面排序的过程中需要注意数据结构的变化。

三 题解

1.两个红黑树反着赋值。

思路:考虑到c++中的map底层是红黑树实现的,会对key值排序,其遍历结果默认为从小到大,因此可以选择用map实现,利用map对频率进行排序,则最后反向迭代器遍历出的前k个数即为题目所求。myMap1用来统计各个数字出现的频率,再反向赋值给myMap2,因为频率相同的数字可能会重复,所以要用容器来装,然后用反向迭代器遍历结果。

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int,int>myMap1;
        //因为频率相同的数字可能不止一个,所以要用数组来存
        map<int,vector<int>>myMap2;
        // 数字对应频率映射
        for(int n : nums){
            myMap1[n]++;
        }
        // 频率对应数字映射
        for(auto it = myMap1.begin(); it!= myMap1.end(); it++){
            myMap2[it->second].push_back(it->first);
        }
        vector<int>ans;
        //考虑到myMap2的迭代器可能指向一个数组,所以不能用下标值的方式去给ans赋值
        for(auto it = myMap2.rbegin(); it != myMap2.rend(); it++){
            ans.insert(ans.end(), (it->second).begin(), (it->second).end());
            if(ans.size() == k)
                break;
        }
        return ans;
    }
};

 2.堆

思路:与普通的topK类型题目一致,可以使用小根堆来维护前k个出现频率大的数,而在此基础上要用哈希表来处理数值与频率的对应关系。哈希表处理完映射关系以后,遍历该表,当堆中元素小于k时直接插入,当剩余k+1个元素则要判断其频率和堆顶的大小关系,如果遍历到的元素出现频率比堆顶大,堆顶就应该被弹出,否则就跳过(←所以要写成两个if语句,不能合并写。

class Solution {
public:
    class cmp{
    public:
        bool operator()(pair<int,int>&a,pair<int,int>&b){
            return a.second > b.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int>HSTable;
        for(int n : nums){
            HSTable[n]++;
        }
        priority_queue<pair<int,int>,vector<pair<int,int>>,cmp>Q;
        for(auto [num,counts] : HSTable){
            if(Q.size() >= k){
                if(counts > Q.top().second){//不能合并成一个if语句
                    Q.pop();
                    Q.emplace(num,counts);
                }
            }else
                Q.emplace(num,counts);
        }
        vector<int>ans;
        while(!Q.empty()){
            ans.emplace_back(Q.top().first);
            Q.pop();
        }
        return ans;
    }
};

注:

1.emplace、emplace_back、emplace_front分别对应insert、push_back、push_front,是C++11增加的新特性,相比push_back更为优化,不用移动或拷贝内存。

2.有关增强型循环的理解:

for(auto a : b) 用a遍历容器b获取其每一个值,但是不能通过修改a来修改b容器中的元素。

for(auto &a : b)传引用的方式,使得可以通过a对容器b内的元素进行更改。

3.重载 < 运算符

C++中的sort()是单调递增的,也就是本来是 < ,但如果把小于号重载成大于号,那么元素就是单调递减了,而c++中的堆就相当于sort数组完以后,把最后一个元素当作堆顶。因此重载成大于号以后,本来C++默认底层是大根堆的优先队列就会变成小根堆。

4.仿函数

仿函数是使一个类的使用看上去像一个函数,实际上就是在类中实现了operator(),使这个类具有类似函数的行为,所以是仿函数类。而我们通常写的greater<int>或者less<int>也是仿函数。

关于自定义排序的问题可以具体看:C++中的仿函数functor_我的大学-CSDN博客_c++仿函数

 3.快速选择

思路也差不多,频率用哈希表处理,分治地选择其中一边来快排。想要实现升序或降序只用更改partition()里两个while循环对cur[r].second和pivot之间大于或小于的关系。

class Solution {
public:
    void randomParttition(vector<pair<int,int> >& cur, int l, int r){
        int pivot = rand() % (r-l+1) + l;
        swap(cur[l],cur[pivot]);
    }
    int partition(vector<pair<int,int> >& cur, int l, int r){
        randomParttition(cur,l, r);
        int pivot = cur[l].second;
        int value = cur[l].first;
        while(l < r){
            while(l < r && cur[r].second <= pivot) --r;
            cur[l] = cur[r];
            while(l < r && cur[l].second >= pivot) ++l;
            cur[r] = cur[l];
        }
        cur[l].first = value;
        cur[l].second = pivot;
        return l;
    }
    void quickSort(vector<pair<int,int> >& cur, int l, int r, int k){
        if(l >= r)  return;
        int pivot = partition(cur,l,r);
        if(pivot == k)  return;
        if(pivot < k)
            quickSort(cur,pivot+1,r,k);
        if(pivot > k)
            quickSort(cur,l,pivot-1,k);
    }
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int>HSTable;
        // 哈希表存入数字对应频率的映射
        for(int i : nums){
            HSTable[i]++;
        }
        vector<pair<int,int> >cur;
        // 初始化待快排的数组
        for(auto j : HSTable){
            cur.emplace_back(j);
        }
        quickSort(cur,0,cur.size()-1,k-1);
        vector<int>ans(k);
        for(int i = 0; i < k; i++){
            ans[i] = cur[i].first;
        }
        return ans;
    }
};

4.二叉搜索树

思路基本跟小根堆那个做法是一样的,先用哈希表统计频率,然后建造一个大小为k的二叉搜索树来维护前k个频率大的数。

举报

相关推荐

0 条评论