前 K 个高频元素

问题描述

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2

输出: [1,2]

示例 2:

输入: nums = [1], k = 1

输出: [1]

说明:

你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。

你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。

题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。

输出时,首先输出频率最高的元素,如果频率相同,则先输出元素值较大的元素。

可使用以下main函数:

int main()

{

    int n,data,k;

    vector<int> nums;

    cin>>n;

    for(int i=0; i<n; i++)

    {

        cin>>data;

        nums.push_back(data);

    }

    cin>>k;

    vector<int> res=Solution().topKFrequent(nums,k);

    for(int i=0; i<res.size(); i++)

    {

        if (i>0)

            cout<<" ";

        cout<<res[i];

    }

    return 0;

}

输入说明

首先输入nums数组的长度n,

然后输入n个整数,以空格分隔。

最后输入k。

输出说明

首先输出频率最高的元素,如果频率相同,则先输出元素值较大的元素。

元素之间以空格分隔。

输入范例

8
1 1 1 2 2 3 3 4
3

输出范例

1 3 2

实现思路及知识点
1. 用什么数据结构

因为题目求前k个高频元素,且要求算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。用堆是一个比较好的解决办法。

2. 实现思路

使用最小堆来实现求前k个高频元素,依次遍历数据,得到对应频率后依次处理该数据,若此时堆的个数小于k则将数据入堆,否则判断该数据的频率与堆顶数据频率进行比较:若大于则移除对顶元素并将该输入如队,否则处理下一个数据直至处理完所有数据。

最后堆里所有元素便是所求答案。

3. 思考过程及所需知识点

a. 为什么用最小堆?

可以举个简单的找最大值的例子:通常我们会设置一个变量x让其尽可能的小,然后开始遍历数据,当数据大于变量x时,就将该数据复制给x,直至遍历完所有数据。所以同理用最小堆来实现。

b. 如何让每个数字与其频率联系起来?

使用map存储键值对,就可以很方便的实现数字与频率的对应,map里的术语是键与值的对应。

map所涉及的一些语法(只列举本题所需要的)

//需包含头文件
#include <map>
//声明map
std::map<key_type, value_type> myMap;//key_type是键的类型,value_type是值的类型
//插入元素
myMap[key] = value;
//访问元素
value = myMap[key];
//遍历map
for (std::map<key_type, value_type>::iterator it = myMap.begin(); it != myMap.end(); ++it) {
    std::cout << it->first << " => " << it->second << std::endl;
}

c. 实现最小堆的priority_queue如何使用?

priority_queue<Type, Container, Functional>,其中Type 为数据类型,Container为保存数据的容器,Functional 为元素比较方式。

//头文件:
#include <queue>
priority_queue<int,std::vector<int>,std::greater<int>> small_heap;//最小堆构造方法
priority_queue<int,std::vector<int>,std::less<int>> big_heap;//最大堆构造方法

std::greater是C++标准库头文件中定义的一个函数对象,用于执行大于比较操作。它是一个模板类,通常与各种容器和算法一起使用。

在优先队列的堆结构中,比较器的作用是判断‘谁应该下沉’而不是‘上浮’,比如std::greater<int>,a>b则a下沉,b上浮,所以堆顶是最小元素 。

d. 声明堆的时候数据类型用什么?

若用int来存储数据的频率,最终也能实现存前k个频率最大的元素,但问题是如何实现最终输出的效果为:首先输出频率最高的元素,如果频率相同,则先输出元素值较大的元素?太麻烦。

用map?map的特性是map中的元素会按照键的顺序自动排序,但我们用priority_queue的第三个参数实现排序,此时map中的排序不是我们所需的,当然也可以自定义比较函数,但C++提供了更方便的数据类型,所以不用map。

C++提供了pair。每个pair 可以存储两个值(两个类型名的类型可以不同)。这两种值无限制。也可以将自己写的struct的对象放进去。pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用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的公有数据成员

综上:

priority_queue<pair<int ,int>,std::vector<pair<int,int>>,std::greater<pair<int,int>>> small_heap;

可实现先按照pair的first元素升序,first元素相等时按second元素升序。

实现代码
#include<iostream>

#include<stack>

#include<algorithm>

#include<vector>

#include<map>

#include<queue>

using namespace std;

class Solution{
public:
    vector<int> topKFrequent(vector<int> &nums,int k){
         map<int,int>mymap;//求频率
         for(int i = 0;i<nums.size();i++){
            mymap[nums[i]]++;
         }

         priority_queue<pair<int ,int>,std::vector<pair<int,int>>,std::greater<pair<int,int>>> small_heap;

         for(std::map<int,int>::iterator it = mymap.begin();it!=mymap.end();++it){
            if(small_heap.size() < k){
                small_heap.push(make_pair(it->second,it->first));
            }else{
                if(it->second >= small_heap.top().first){
                    small_heap.pop();
                    small_heap.push(make_pair(it->second,it->first));

                }
            }
         }

         vector<int> res;
         while(!small_heap.empty()){
            res.push_back(small_heap.top().second);
            small_heap.pop();
         }

         //因为是小顶堆,每次从堆顶弹出元素,导致最终结果与想要的是相反的,所以需要进行一次reverse操作
         reverse(res.begin(),res.end());

         return res;




    }
};

int main()

{

    int n,data,k;

    vector<int> nums;

    cin>>n;

    for(int i=0; i<n; i++)

    {

        cin>>data;

        nums.push_back(data);

    }

    cin>>k;

    vector<int> res=Solution().topKFrequent(nums,k);

    for(int i=0; i<res.size(); i++)

    {

        if (i>0)

            cout<<" ";

        cout<<res[i];

    }

    return 0;

}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值