前 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. 使用rpmbuild打包erlang和rabbitmq进行部署服务的方法

    使用rpmbuild打包erlang和rabbitmq进行部署服务的方法 背景说明 1. rabbitmq 是基于 erlang 开发的消息列队, 本身rabbitmq 自己不区分架构. 2. 但是e ...

  2. js数组修改后会互相影响

    // 假设httpServe 是服务器返回来的数据 // 我们这里有一个需求, // 某一个区域需要对这一份数据进行展示 // 另一个区域需要只需要展示前1条数据 let httpServe = [ ...

  3. echarts设置标题样式

    <!DOCTYPE html> <html> <!-- https://blog.csdn.net/weixin_42698255/article/details/892 ...

  4. 使用 NuGet.Server 创建和部署 ASP.NET Web 应用程序搭建私有Nuget服务器

    使用 NuGet.Server 创建和部署 ASP.NET Web 应用程序搭建私有Nuget服务器 在Visual Studio中,选择"新建>文件>"Project ...

  5. MySQL 之多表连查(精简笔记)

    MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品.MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RD ...

  6. Tomcat8安装手记

    Tomcat安装虽然简单,稍不注意,就会坠入万丈深渊,记录痛苦的安装经历. 首先先介绍一下安装条件和正确的安装方式. 安装条件 系统已经安装jdk(前提) tomcat8压缩包 (可以去官网下载 或者 ...

  7. ProTab(高级表格)的使用

    一. params 和 request 属性的使用 例子如下: import React, { useState } from 'react'; import { ProTable } from '@ ...

  8. Dash 2.15版本新特性介绍

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/dash-master 大家好我是费老师,Dash不久前发布了其2.15.0版本,新增了一些实用的特性 ...

  9. PVE上启用Intel核显的SR-IOV vGPU

    介绍 Intel SR-IOV vGPU是一种硬件虚拟化技术,它允许多个虚拟机共享单个物理GPU,而不会降低性能.SR-IOV定义了一种标准方法,通过将设备分区为多个虚拟功能来共享物理设备功能.每个虚 ...

  10. Java并发(八)----使用线程避免cpu占用100%

    1.sleep 实现 在没有利用 cpu 来计算时,不要让 while(true) 空转浪费 cpu,这时可以使用 yield 或 sleep 来让出 cpu 的使用权给其他程序 while(true ...