问题描述
给定一个非空的整数数组,返回其中出现频率前 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;
}