Leetcode-347 前k个高频元素

发布于 2022-03-18  618 次阅读


题目

题目并不难,首先遍历一次数组,用哈希表计数,然后对次数排序,返回前k个值就行。

记录一下unordered_map的用法方便以后查阅。

首先unordered_map与map的区别在于是否排序,前者实现是哈希表,后者实现是红黑树,其他函数基本相同,都是用pair作为存储单元。

pair结构相对简单,就是两个值组成一个变量。

pair<T1, T2> p1;            //创建一个空的pair对象(使用默认构造),它的两个元素分别是T1和T2类型,采用值初始化。
pair<T1, T2> p1(v1, v2);    //创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2。
make_pair(v1, v2);          // 以v1和v2的值创建一个新的pair对象,其元素类型分别是v1和v2的类型。
p1 < p2;                    // 两个pair对象间的小于运算,其定义遵循字典次序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回true。
p1 == p2;                  // 如果两个对象的first和second依次相等,则这两个对象相等;该运算使用元素的==操作符。
p1.first;                   // 返回对象p1中名为first的公有数据成员
p1.second;                 // 返回对象p1中名为second的公有数据成员

unordered_map详细用法见详细介绍C++STL:unordered_map - 朤尧 - 博客园 (cnblogs.com)

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> times;
        for(auto i: nums){
            if(times.find(i)==times.end())
                times.insert({i,1});
            else
                times[i]++;
        }
        vector<pair<int,int>> tmp;
        for(auto i:times)
            tmp.push_back(i);
        sort(tmp.begin(), tmp.end(), [=](std::pair<int, int>& a, std::pair<int, int>& b) { return a.second > b.second; });
        vector<int> answer;
        for(int i=0;i<k;i++){
            answer.push_back(tmp[i].first);
        }
        return answer;
    }
};

注意到对unordered_map排序可以用vector的sort,并自定义比较函数。可以直接写在sort参数里,也可以在外部写好,将函数名传进去。

这里[=]lambda函数的变量引用方式,详见C++之Lambda表达式 - 季末的天堂 - 博客园 (cnblogs.com)

这样写问题不大,但效率浪费在我们只要前k个值,并不需要对整个数组排序,这种只要排序前k个值的情况很适合用堆来实现,这题是正序就用大顶堆。

大顶堆之前的文章也写过实现方式,但没写stl库里的priority_queue用法,因此这里补上。

常规的priority_queue的定义如下
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆

//升序队列
priority_queue <int,vector<int>,greater<int> > q;
//降序队列
priority_queue <int,vector<int>,less<int> >q;

//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)

但是这题我们需要将堆的元素改为pair,但使用pair的优先队列的比较函数是先比较first再比较second,因此还需要重写比较函数。参考自定义类型的优先队列方法定义符合要求的priority_queue

#include <iostream>
#include <queue>
using namespace std;

//方法1
struct tmp1 //运算符重载<
{
    int x;
    tmp1(int a) {x = a;}
    bool operator<(const tmp1& a) const
    {
        return x < a.x; //大顶堆
    }
};

//方法2
struct tmp2 //重写仿函数
{
    bool operator() (tmp1 a, tmp1 b) 
    {
        return a.x < b.x; //大顶堆
    }
};

int main() 
{
    tmp1 a(1);
    tmp1 b(2);
    tmp1 c(3);
    priority_queue<tmp1> d;
    d.push(b);
    d.push(c);
    d.push(a);
    while (!d.empty()) 
    {
        cout << d.top().x << '\n';
        d.pop();
    }
    cout << endl;

    priority_queue<tmp1, vector<tmp1>, tmp2> f;
    f.push(c);
    f.push(b);
    f.push(a);
    while (!f.empty()) 
    {
        cout << f.top().x << '\n';
        f.pop();
    }
}

将解法进行修改

class Solution {
public:
    static bool cmp(pair<int,int> &a,pair<int,int> &b){return a.second>b.second;}//static限制函数作用范围
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> times;
        for(auto i: nums){
            if(times.find(i)==times.end())
                times.insert({i,1});
            else
                times[i]++;
        }
        priority_queue<pair<int,int>,vector<pair<int,int>>,decltype(&cmp)> temp(cmp);
        for(auto i:times){
            if(temp.size()<k)
                temp.push(i);
            else{
                if(i.second>temp.top().second){
                    temp.pop();
                    temp.push(i);
                }
            }
        }
        vector<int> answer;
        while(!temp.empty()){
            answer.push_back(temp.top().first);
            temp.pop();
        }
        return answer;
    }
};
反向优化了属于是

至于仿函数,decltype等搞懂后在更吧

还是写个struct易懂一些

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
    //1.map记录元素出现的次数
        unordered_map<int,int>map;//两个int分别是元素和出现的次数
        for(auto& c:nums){
            map[c]++;
        }
    //2.利用优先队列,将出现次数排序
        //自定义优先队列的比较方式,小顶堆
        struct myComparison{
            bool operator()(pair<int,int>&p1,pair<int,int>&p2){
                return p1.second>p2.second;//小顶堆是大于号
            }
        };
        //创建优先队列
        priority_queue<pair<int,int>,vector<pair<int,int>>,myComparison> q;
        //遍历map中的元素
        //1.管他是啥,先入队列,队列会自己排序将他放在合适的位置
        //2.若队列元素个数超过k,则将栈顶元素出栈(栈顶元素一定是最小的那个)
        for(auto& a:map){
            q.push(a);
            if(q.size()>k){
               q.pop(); 
            }
        }
        //将结果导出
        vector<int>res;
        while(!q.empty()){
            res.emplace_back(q.top().first);
            q.pop();
        }
        return res;

    }
};