前 K 个高频元素

力扣题目链接(opens new window)

给定一个非空的整数数组,返回其中出现频率前 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 个高频元素的集合是唯一的。
  • 你可以按任意顺序返回答案

思路

(两个解法,记一种,不要乱就行)

初见思路是:遍历数组,根据下标在set中记录元素出现的次数,遍历结束,将set中的元素放入优先级队列中排序,取出前k个元素

显然是不行的,虽然将获取到了元素出现的次数并排序,但是丢失了原始数组中元素的位置信息导致无法返回对应元素

所以应该用map啊,key对应数组中的元素,value对应其出现过的次数

统计完成之后,再以value为对象进行一个排序,从而得到前k个高频出现的元素

怎么排序?

这种数据结构比较合适,具体来说就是 大顶堆小顶堆

因为题目对时间复杂度有要求,所以以前通小顶堆来做可以满足要求(限制堆的大小进而控制复杂度)

但是现在好像两种方式都可以了

使用小顶堆

我们只需将堆的大小规定为k,然后不断往堆里push元素,同时将多余的元素pop掉,最后就剩下想要的前k个元素了。

注意,这里具体我们应该使用的是小顶堆

举个例子,假设k = 3,我们使用小顶堆

   1
/ \
2 3

接下来往堆中push进一个新value,那么堆顶的元素就要被pop掉,然后堆中元素重新排序,使新的最小值位于堆顶

       1                   2
/ \ / \
2 3 → 3 6
/
6

如此循环,最小值不断被pop掉,最后这个大小为k的小顶堆中剩下的就是前k个最大的值

这也就是为什么用小顶堆而不是大顶堆的原因,用大顶堆的话效果正好相反(在限制k的情况下),会得到前k个最小的值

以value(也就是元素出现次数)排序好之后,只需再将对应的key返回即可

使用大顶堆

这种方式就很直观,但是相比小顶堆(限制k的值)而言复杂度会高一些

遍历数组,将所有元素放入堆中,此时堆顶就是最大值,然后我们只需将堆顶元素pop出来k次,即可得到前k个高频元素

补充知识:pair容器

参考

简介

pair是一种将两个数据组合成一个数据的容器(如stl中的map就是将key和value放在一起来保存)

pair<T1, T2> p1(v1, v2);

       p1
-----------------
| T1 V1 | T2 V2 |

如上图,v1、v2组合之后又变成了一个数据p1

当想要在一个函数中返回两个值的时候也可以用pair,先将数据打包成一个,返回之后在解包

定义

其标准库类型 --pair 类型定义在#include<utility>头文件中,定义如下:

类模板:template<class T1,class T2> struct pair

参数:T1是第一个值的数据类型,T2是第二个值的数据类型。

功能:pair将一对值(T1和T2)组合成一个值,

​ 这一对值可以具有不同的数据类型(T1和T2),

​ 两个值可以分别用pair的两个公有函数first和second访问。

构造函数
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的公有数据成员
使用

利用make_pair创建新的pair对象

 pair<int, double> p1;
p1 = make_pair(1, 1.2); cout << p1.first << p1.second << endl; //output: 1 1.2 int a = 8; string m = "James"; pair<int, string> newone; newone = make_pair(a, m);
cout << newone.first << newone.second << endl; //output: 8 James

补充知识:优先级队列

参考

简介

普通的队列具有先进先出的特性,元素追加在队尾,如果删除的话,从队头删除。

而在优先队列中,队列中的数据被赋予了优先级

当访问元素时,优先级最高的会先被删除;所以说优先队列是最高级数据先出

定义

其位于头文件#include <queue>

基本操作:

  • empty()    如果队列为空,则返回真
  • pop()    删除对顶元素,删除第一个元素
  • push()    加入一个元素
  • size()     返回优先队列中拥有的元素个数
  • top()     返回优先队列对顶元素,返回优先队列中有最高优先级的元素

在默认的优先队列中,优先级高的先出队。在默认的int型中先出队的为较大的数。

声明
普通方式
priority_queue<vector<int>, less<int> > pq1;     // 使用递增less<int>函数对象排序

priority_queue<deque<int>, greater<int> > pq2;   // 使用递减greater<int>函数对象排序

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

通过构建一个仿函数去规定优先级队列的比较规则进而实现自定义优先级

struct cmp {//自定义的比较仿函数
  operator bool ()(int x, int y){
     return x > y;// x小的优先级高
//也可以写成其他方式,如: return p[x] > p[y];表示p[i]小的优先级高
  }
}; priority_queue<int, vector<int>, cmp> q; //定义方法 //其中,第二个参数为容器类型。第三个参数为比较函数。
使用

用pair做优先队列元素:先比较frist,如果frist相等,那么就比较第二个

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
priority_queue<pair<int, int> > a;
pair<int, int> b(1, 2);
pair<int, int> c(1, 3);
pair<int, int> d(2, 5);
a.push(d);
a.push(c);
a.push(b);
while (!a.empty())
{
cout << a.top().first << ' ' << a.top().second << '\n';
a.pop();
}
}

代码

大顶堆实现的代码比起小顶堆的要简单一些

大顶堆实现
步骤

1、创建unordered_map,遍历数组,在map中对应位置记录

2、以pair作为元素创建优先级队列priority_queue,遍历map(iterator方式)

  • ​ 定义pair时使用 [值,键] 的方式,也就是让频率在前,对应元素在后。因为如果没有自定义优先级,使用pair的priority_queue会默认先比较第一个值进行排序(当然还是构成大顶堆)

3、定义结果数组res,从堆顶循环取值k次,将pair中的second值取出放入res,同时pop掉当前值

代码
// 时间复杂度:O(nlogn)
// 空间复杂度:O(n)
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
//创建unorder_map
unordered_map<int, int> map;
//遍历nums,在map中对应位置记录
for(int i = 0; i < nums.size(); ++i){
map[nums[i]]++;
}
//创建优先级队列priority_queue
priority_queue<pair<int, int>> que;//大顶堆
//遍历map
for(auto pair : map){
//取出pair中的数据,以[值,键] 的方式加入大顶堆
que.push(make_pair(pair.second, pair.first));
}
vector<int> res;
while(k--){
res.push_back(que.top().second);//即将pair.first(元素值)放入res中
que.pop();
}
return res;
}
};
不熟练的点

1、map的构造

构造:

  • map<T1, T2> mp; //map默认构造函数:
  • map(const map &mp); //拷贝构造函数

2、队列的使用方法

构造函数:

  • queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
  • queue(const queue &que); //拷贝构造函数

赋值操作:

  • queue& operator=(const queue &que); //重载等号操作符

数据存取:

  • push(elem); //往队尾添加元素
  • pop(); //从队头移除第一个元素
  • back(); //返回最后一个元素
  • front(); //返回第一个元素

大小操作:

  • empty(); //判断堆栈是否为空
  • size(); //返回栈的大小
小顶堆实现
仿函数

使用小顶堆实现需要自定义优先级规则,如下:

通过构建一个仿函数定义如何比较两个 pair

输入为两个pair的引用,比较的是pair中的second值

struct cmp {//自定义的比较仿函数
  bool operator()(const pair<int, int>& lefths, const pair<int, int>& righths) {
return lefths.second > righths.second;//从小到大
}
};

这里比较阴间的是:左大于右就会建立小顶堆,反之则建立大顶堆

刚好和认知相反

步骤

1、创建一个仿函数,定义优先级规则

2、创建unordered_map,遍历数组,对应位置记录

3、创建priority_queue

4、遍历map,pair存入队列

  • 当队列元素个数大于k时,pop掉当前队首元素

5、创建vector res,逆序遍历队列获取结果

  • res直接创建大小为k的就行

  • 注意,这里需要获取的是pair的first

代码
class Solution {
public: class cmp{
public:
bool operator()(pair<int, int>& left, pair<int, int>& right){
return left.second > right.second;
}
}; vector<int> topKFrequent(vector<int>& nums, int k) {
//定义map
unordered_map<int, int> map;
for(int i = 0; i < nums.size(); ++i){
map[nums[i]]++;
}
//定义优先级队列
priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> que;
for(unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++){
que.push(*it);
//控制堆的大小
if(que.size() > k){
que.pop();
}
}
//使用了 vector<int> res(k) 来初始化 res,是因为我们知道 res 的长度是 k,所以可以直接指定它的大小
vector<int> res(k);
//逆序遍历
for(int i = k - 1; i >= 0; --i){
res[i] = que.top().first;//往 res 中添加元素时使用下标访问方式赋值是因为在遍历的过程中,我们需要保证各个元素填充到正确的位置上,而 push_back() 只能将元素添加到数组的最后面
//如果使用 push_back() 方法来添加元素,则需要在最后再执行一次 reverse(res.begin(), res.end()) 来翻转数组,降低了效率
que.pop();
} return res;
}
};
不熟练的点

1、vector创建的方式

要创建指定大小的vector,用括号

例如:`vector res(10);

二刷问题

1、自定义优先级队列的比较规则时不熟练,operator写错(多写点)

2、使用迭代器iterator来遍历map的操作不熟练

3、创建键值对可以使用make_pair(),也可以使用花括号

4、大小根堆概念有遗忘

5、使用下标方式填充vector的操作不熟练

【LeetCode栈与队列#06】前K个高频元素(TopK问题),以及pair、priority_queue的使用的更多相关文章

  1. leetcode的Hot100系列--347. 前 K 个高频元素--hash表+直接选择排序

    这个看着应该是使用堆排序,但我图了一个简单,所以就简单hash表加选择排序来做了. 使用结构体: typedef struct node { struct node *pNext; int value ...

  2. 代码随想录算法训练营day12 | leetcode 239. 滑动窗口最大值 347.前 K 个高频元素

    基础知识 ArrayDeque deque = new ArrayDeque(); /* offerFirst(E e) 在数组前面添加元素,并返回是否添加成功 offerLast(E e) 在数组后 ...

  3. 【LeetCode题解】347_前K个高频元素(Top-K-Frequent-Elements)

    目录 描述 解法一:排序算法(不满足时间复杂度要求) Java 实现 Python 实现 复杂度分析 解法二:最小堆 思路 Java 实现 Python 实现 复杂度分析 解法三:桶排序(bucket ...

  4. LeetCode:前K个高频元素【347】

    LeetCode:前K个高频元素[347] 题目描述 给定一个非空的整数数组,返回其中出现频率前 k 高的元素. 示例 1: 输入: nums = [1,1,1,2,2,3], k = 2 输出: [ ...

  5. Java实现 LeetCode 347 前 K 个高频元素

    347. 前 K 个高频元素 给定一个非空的整数数组,返回其中出现频率前 k 高的元素. 示例 1: 输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2] 示例 2: 输 ...

  6. 代码随想录第十三天 | 150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素

    第一题150. 逆波兰表达式求值 根据 逆波兰表示法,求表达式的值. 有效的算符包括 +.-.*./ .每个运算对象可以是整数,也可以是另一个逆波兰表达式. 注意 两个整数之间的除法只保留整数部分. ...

  7. Top K Frequent Elements 前K个高频元素

    Top K Frequent Elements 347. Top K Frequent Elements [LeetCode] Top K Frequent Elements 前K个高频元素

  8. leetcode347. 前 K 个高频元素

    题目最终需要返回的是前 kk 个频率最大的元素,可以想到借助堆这种数据结构,对于 kk 频率之后的元素不用再去处理,进一步优化时间复杂度. 具体操作为: 借助 哈希表 来建立数字和其出现次数的映射,遍 ...

  9. 力扣 - 347. 前 K 个高频元素

    目录 题目 思路1(哈希表与排序) 代码 复杂度分析 思路2(建堆) 代码 复杂度分析 题目 347. 前 K 个高频元素 思路1(哈希表与排序) 先用哈希表记录所有的值出现的次数 然后将按照出现的次 ...

  10. 前 K 个高频元素问题

    前 K 个高频元素问题 作者:Grey 原文地址: 前 K 个高频元素问题 题目描述 LeetCode 347. Top K Frequent Elements 思路 第一步,针对数组元素封装一个数据 ...

随机推荐

  1. Go中字符串处理:fmt.Sprintf与string.Builder的比较

    在Go语言中,我们通常会遇到两种主要的方式来处理和操作字符串:使用fmt.Sprintf函数和string.Builder类型.尽管两者都可以实现字符串的格式化和连接,但它们在性能和用法上有一些关键区 ...

  2. Gin 框架介绍与快速入门

    Gin 框架介绍与快速入门 目录 Gin 框架介绍与快速入门 一.Gin框架介绍 1. 快速和轻量级 2. 路由和中间件 3. JSON解析 4. 支持插件 5. Gin相关文档 二.基本使用 1.安 ...

  3. 不同版本的Unity要求的NDK版本和两者对应关系表(Unity NDK Version Match)

    IL2CPP需要NDK Unity使用IL2CPP模式出安卓包时,需要用到NDK,如果没有安装则无法导出Android Studio工程或直接生成APK,本篇记录一下我下载NDK不同版本的填坑过程. ...

  4. TienChin 活动管理-删除活动

    后端 ActivityController.java @PreAuthorize("hasPermission('tienchin:activity:remove')") @Log ...

  5. TienChin 渠道管理-渠道导出

    ChannelController /** * 导出渠道列表 */ @PreAuthorize("hasPermission('tienchin:channel:export')" ...

  6. 8.4 ProcessHeap

    ProcessHeap 是Windows进程的默认堆,每个进程都有一个默认的堆,用于在进程地址空间中分配内存空间.默认情况下ProcessHeap由内核进行初始化,该堆中存在一个未公开的属性,它被设置 ...

  7. 配置VSFTP文件服务器

    FTP 文件传输协议.用于互联网上的控制文件的双向传输,使用FTP来传输时,其实是具有一定程度的危险性,因为数据在因特网上面是完全没有受到保护的明文传输方式,VSFTP是一个基于GPL发布的类Unix ...

  8. 纪念JDBC

    技术总是在不断更新变化的,尤其是在IT编程领域. 有时候我们理所当然的用着现成的框架,以至于用的太过于顺手,更要时不时的骂一句: 什么垃圾框架?我家狗都不会用! 如果那些被拍死在沙滩的"前浪 ...

  9. 苹果M3 Max有两种版本:14+40?还是16+40?

    最近有关苹果M3系列处理器的消息突然多了起来,包括M3.M3 Pro.M3 Max,都将升级为台积电3nm工艺,但规格上比较保守,至少核心数量不会大幅增加. 此前说法称,M3 Max将配备14个CPU ...

  10. 曝iPhone 15系列将于9月13日发布 9月22日发售:7大升级、或售5999元起

    按照往年惯例,新款iPhone将于9月中下旬(第三周)与大家见面.9to5Mac今日带来了新款iPhone的最新消息--iPhone 15系列将于9月13日发布,9月22日正式发售. 9to5Mac从 ...