算法力扣刷题 三十三【347.前 K 个高频元素】

前言

栈和队列篇。
记录 三十三【347.前 K 个高频元素】


一、题目阅读

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高元素。你可以按 任意顺序 返回答案。

示例 1:

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

示例 2:

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

提示:

1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:

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

二、尝试实现

思路一

用哈希法,因为涉及到元素——次数,可以想到map这种结构。
(1)先统计每个元素出现的次数:元素作为key,次数作为value。不需要有序,不允许重复。所以定义一个unordered_map;
(2)再把次数调整为key,元素作为value。因为要对次数排序,操作的是次数,所以key需要改成次数。最好是有序(这样就不用排了),次数可能重复。所以定义一个multimap。
(3)返回结果把multimap内后k个取出来,获取second,得到result。

代码实现一

class Solution {
public:vector<int> topKFrequent(vector<int>& nums, int k) {unordered_map<int,int> map;multimap<int,int> s;//统计次数for(int i = 0;i < nums.size();i++){map[nums[i]]++;}//调整次数为key,元素作为valuefor(auto it = map.begin();it != map.end();++it){s.insert(pair<int,int>(it->second,it->first));}vector<int> result;auto it = s.end();//把multimap的后k个取出来,push_back元素,返回结果。while(k--){it--;result.push_back(it->second);}return result;}
};

时间复杂度:统计次数,遍历nums长度为n;当放到multimap中长度也是n;再找后k个取值的时候,是log(n)。所以O(n log n)。

思路二

记录 三十二中有单调队列的使用。这里能不能也自定义单调队列,实现频率前k高的元素维护?
(1)队列用deque容器,放置类型pair<int,int>,对值,first是次数,second是元素。
(2)把nums先排序,再遍历结束之前无法确定频率前k高是谁,只能都暂时保留记录,让单调队列里面次数单调递减。需要一个辅助结构来调整顺序。
(3)需要哪些函数呢?先完成主函数,再补充单调队列功能。(看代码注释)

代码实现二

class Solution {
public:
class Dq{deque<pair<int,int>> dq;//想dq中大小是k,只放前k高的频率。stack<pair<int,int>> st;//辅助结构int size;
public:Dq(int k):size(k){}void push(pair<int,int> val){	while(!dq.empty() && val.first > dq.back().first){st.push(dq.back());dq.pop_back();}dq.push_back(val);this->resizeDQ();}void resizeDQ(){while(dq.size() < size && !st.empty()){    //dq放置前k高频率dq.push_back(st.top());st.pop();}return;}int pop(){	//调整为直接返回元素。int num = dq.front().second;dq.pop_front();return num;}
};vector<int> topKFrequent(vector<int>& nums, int k) {//不确定size == 1,边界情况在for循环中包不包含,所以先写上,发现for循环可以包含所以注释掉。// if(nums.size() == 1){	//     return nums;// }sort(nums.begin(),nums.end());	//先排序。重复的肯定紧挨着,可以判断下一个和前一个是否相等//遍历nums,找每个元素的出现次数。int count = 1;	 //初始化为1Dq que(k);	for(int i = 1;i < nums.size();i++){if(nums[i] != nums[i-1]){pair<int,int> p(count,nums[i-1]);	que.push(p);	//统计出来一个元素的出现次数。放入单调队列中count = 1;}else{count++;}}que.push(pair<int,int> (count,nums[nums.size()-1]));	//要把最后的也放进去。vector<int> result;while(k--){result.push_back(que.pop());	//从单调队列中弹出频率前k高的元素。}return result;}
};

三、参考思路

参考思路链接

学习内容

思路:
(1)数据结构:大顶堆和小顶堆。用来在集合里求前k个高频或低频结果。堆底层实现完全二叉树。

  • 大顶堆:父亲始终大于左右两个孩子,所以最高点是最大的值;
  • 小顶堆:父亲始终小于左右两个孩子,所以最高点是最小的值;

(2)如何用堆实现?选大顶堆还是小顶堆?

  • 堆的操作
    • 插入元素:先把元素放到堆的末尾,再和父节点比较,进行上浮,调整大小,使得满足大小;
    • 删除元素:结果:弹出堆顶元素。先把堆顶元素和最后元素交换,再进行浮动调整;
  • 题目要获得高频前k个,设置堆内只有k个元素,如果放进来一个,那么,pop一个,pop的是堆顶元素,应该把最小的pop出去,留下大的值。所以选择小顶堆

(3)C++中有没有堆的结构可以直接实现?
#include < queue >中有priority_queue类。(这个类的解释见补充

代码实现

学习完priority_queue和思路,先尝试按该思路实现。再对比参考注意细节问题。

  • 因为priority_queue的默认比较函数是less,默认构成大顶堆。所以要自定义比较函数
  • priority_queue内元素类型是pair<int,int>,first是出现次数,second是元素值。
class Solution {
public:
class Mycmp{
public:bool operator() (const pair<int,int>& x,const pair<int,int>& y) const{return x.first >= y.first;   //当值越大越是true,排序下来最小的在堆顶}
};vector<int> topKFrequent(vector<int>& nums, int k) {unordered_map<int,int> map;//统计元素出现的次数priority_queue<pair<int,int>,vector<pair<int,int>>,Mycmp> que;for(int i = 0;i < nums.size();i++){ //元素值作为key,次数作为value。重新调整。map[nums[i]]++;}for(auto it = map.begin();it != map.end();++it){pair<int,int> p(it->second,it->first);//构造pair元素,次数作为比较的对象。if(que.size() < k){ //堆内不足k个,直接放入que.push(p);}else if(que.size() == k){que.push(p);que.pop();}}//处理堆剩下的元素vector<int> result;while(!que.empty()){result.push_back(que.top().second);que.pop();}return result;}
};

对比参考代码,改进之处:

  1. 参考在compare函数中直接比较second,不需要再pair<int,int>构造,把map中的值放进来;
  2. 放入堆时,if-else if都需要push,统一起来。只if(size > k) 的情况。

总结

  • 本道题用了3种代码实现:
    • 第一种:看到统计次数,想能不能用哈希结构?所以先统计次数,再调整key和value,用有序且需要重复的multimap结构排序。最后取出后k个。
    • 第二种:实现一个DQ单调队列,为所有出现次数进行排序,维护DQ队列中出现次数递减。应用记录 三十二中所学单调队列的方法。
    • 第三种:使用堆结构,C++中priority_queue类,构造小顶堆。保持排序的完全二叉树结构。
  • 下面补充priority_queue使用方法:

补充:priority_queue类

概念

优先级队列。

  • 用处:使用堆结构,堆可以用来优先级确定、排序。在< queue >头文件中。
  • 只能top()访问堆顶元素,堆顶元素比其他的都要大。
  • priority_queue底层实现容器:vector或deque。这个容器能够操作:empty()、size()、front()、push_back()、pop_back()。默认是vector
    • 提一下vector:push_back()、pop_back()看起来是vector的尾部(“后端”),但从这个后端pop出priority_queue的堆顶。
  • 为什么始终能保持堆结构呢?因为底层容器支持random access iterator,并且priority_queue可以自动调用make_heap、pop_heap、push_heap算法。

定义

template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> >
class priority_queue;

解释:

  • T:队列内元素类型;
  • Container :底层实现默认用vector。
  • Compare:一个比较函数。可以自定义。两个参数类型是T,返回值bool类型。如果a < b,返回true,最后排序是最小到大,priority_queue中pop的是最后一个元素,也就是最大值(大顶堆)。
  • 所以,如果实现小顶堆,需要自定义比较函数,更改方式。默认维护的是大顶堆。
  • 额外:什么是严格弱排序?4条性质,需要自定义函数时检查能否满足下面这4条性质。
    • 自身比较:a < a,return false;
    • 传递性:a < b,b < c,那么a < c,return true;
    • 如果a < b,return true;那么 b < a,return false;
    • 不可比较性:如果a,b带进去无法比较,return false,;b,c带进去无法比较,return false;那么a,c带进去也是不可比较。(带进比较函数里)

构造函数

函数原型:类型是定义类时给的。

比较函数Compare在容器结构Container的前面:
原型一:priority_queue (const Compare& comp, const Container& ctnr);
原型二:template <class InputIterator>  priority_queue (InputIterator first, InputIterator last, const Compare& comp, const Container& ctnr); //first和last可以传指针,代表范围。比如用默认的Container和Compare:
priority_queue<int,vector<int>,less<int>> first; 等同于:priority_queue<int> first;自定义Container和Compare:
class MyCmp{
public:bool operator()(int& a,int&b){return a>b;	//改成小顶堆}
};
priority_queue<int,vector<int>,Mycmp> second;
Mycmp cmp;
priority_queue<int,vector<int>,Mycmp> second(cmp);

成员函数(数量不多)

void empty();//判断空?
size_type size();//大小。元素数量
const_reference top() const;//获取堆顶,返回引用。实际是调用底层的front()
void push (const value_type& val);//放入元素。先调用底层容器的push_back(),再调用push_heap()排序
void push (value_type&& val);
void pop();//移除堆顶。先调用pop_heap函数,再调用底层容器的pop_back(),再调用元素的析构。
void swap (priority_queue& x) noexcept;//交换两个priority_queue的内容和比较函数,同类型的priority_queue,size可以不同。
swap(x,y);//参数x,y是同类型的priority_queue,交换容器的值和比较函数。

总结:empty/size/push/pop/top/swap处理堆内元素。


(欢迎指正,转载表明出处)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/867080.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从草图到现实:SketchUp 在建筑项目中的独特优势

Sketchup 是全球最受欢迎的建筑可视化平台之一。借助该平台提供的各种工具&#xff0c;您可以创建可供市场使用的逼真项目。Sketchup为什么如此优秀&#xff1f;它对建筑项目有哪些优势&#xff1f;下面&#xff0c;你将看到什么是 Sketchup 以及这个工具的一些重要的优势。 关…

新手如何尽快入门性能测试?

一、学习基础知识 性能测试的基本概念&#xff1a; 性能测试的定义&#xff1a;使用自动化工具&#xff0c;模拟不同的场景&#xff0c;对软件各项性能指标进行测试和评估的过程。 性能测试的目的&#xff1a;评估当前系统的能力、寻找性能瓶颈以优化性能、评估软件是否能够满…

高阶算法班从入门到精通之路课程

本课程旨在帮助学员深入理解算法与数据结构的核心概念&#xff0c;从而掌握高级算法设计与分析技能。每集课程内容精心设计&#xff0c;涵盖了常用数据结构、经典算法及其应用场景等方面的深度讲解&#xff0c;同时通过大量实例演练&#xff0c;帮助学员提升解决实际编程难题的…

【Dell R730 折腾记录】风扇调速--在 Ubuntu 系统上开机自启动并每隔30分钟执行一次风扇定速脚本

前段时间升级了一下机柜里的服务器&#xff0c;替换掉了一台旧的 Dell 服务器&#xff0c;换上了这台 R730。但是无奈于噪音的袭扰&#xff0c;搁置了一段时间。我在这台机器上目前安装了一块 Intel Xeon E5-2630v3 芯片以及一张改过散热的 NVIDIA Tesla P4 计算卡。结果就是散…

使用工业自动化的功能块实现大语言模型应用

大语言模型无所不能&#xff1f; 以chatGPT为代表的大语言模型横空出世&#xff0c;在世界范围内掀起了一场AI革命。给人的感觉似乎大模型语言无所不能。它不仅能够生成文章&#xff0c;图片和视频&#xff0c;能够翻译文章&#xff0c;分析科学和医疗数据&#xff0c;甚至可以…

20240706 xenomai系统中网口(m2/minipcie I210网卡)的实时驱动更换

lspci 查看网口 查看网口驱动 1 ubuntu 查看网口驱动 在Ubuntu中&#xff0c;您可以使用lshw命令来查看网络接口的驱动信息。如果lshw没有安装&#xff0c;您可以通过执行以下命令来安装它&#xff1a; sudo apt-get update sudo apt-get install lshw 安装完成后&#xff…

golang与以太坊交互

文章目录 golang与以太坊交互什么是go-ethereum与节点交互前的准备使用golang与以太坊区块链交互查询账户的余额使用golang生成以太坊账户使用golang生成以太坊钱包使用golang在账户之间转移eth安装使用solc和abigen生成bin和abi文件生成go文件使用golang在测试网上部署智能合约…

《昇思25天学习打卡营第12天|onereal》

CycleGAN图像风格迁移互换 模型简介 CycleGAN(Cycle Generative Adversarial Network) 即循环对抗生成网络&#xff0c;来自论文 Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks 。该模型实现了一种在没有配对示例的情况下学习将图像从源域…

C++中的引用——引用做函数参数

作用&#xff1a;函数传参时&#xff0c;可以利用引用的技术让形参修饰实参 优点&#xff1a;可以简化指针修改实参 示例&#xff1a; 1.值传递 运行结果&#xff1a; 2.地址传递 运行结果&#xff1a; 3.引用传递 运行结果&#xff1a;

量化交易策略:人性的弱点之反马丁策略

一、马丁策略与反马丁策略的区别 上一文章介绍了马丁策略,是一种赌徒的加仓策略,即在亏损时不断增加仓位,以期在市场反转时获得更大的收益。这种策略的核心理念是“顺势而为”,即在市场上涨时不断加仓,而在市场下跌时保持仓位不变或者减少仓位。 反马丁策略则是一种保守策…

SQL注入方法

文章目录 前言如何测试与利用注入点手工注入思路工具sqlmap-r-u-m--level--risk-v-p--threads-batch-smart--os-shell--mobiletamper插件获取数据的相关参数 前言 记录一些注入思路和经常使用的工具&#xff0c;后续有用到新的工具和总结新的方法再继续补充。 如何测试与利用注…

windows下使用编译opencv在qt中使用

记录一下&#xff1a;在windows下qt使用opencv 1、涉及需要下载的软件 CMake 下载地址opecnv下载地址mingw(需要配置环境变量) 这个在下载qt的时候可以直接安装一般在qt的安装路径下的tool里比如我的安装路径 (C:\zz\ProgramFiles\QT5.12\Tools\mingw730_64) 2、在安装好CMake…

【IT领域新生必看】探索Java中的对象创建:深入理解`new`与`clone`的对比

文章目录 引言什么是new关键字&#xff1f;使用new关键字的基本语法示例&#xff1a; 什么是clone方法&#xff1f;使用clone方法的基本语法示例&#xff1a; new与clone的区别内存分配与初始化调用方式适用场景性能 new关键字的优缺点优点缺点 clone方法的优缺点优点缺点 深入…

大华设备接入GB28181视频汇聚管理平台EasyCVR安防监控系统的具体操作步骤

智慧城市/视频汇聚/安防监控平台EasyCVR兼容性强&#xff0c;支持多协议接入&#xff0c;包括国标GB/T 28181协议、GA/T 1400协议、部标JT808协议、RTMP、RTSP/Onvif协议、海康Ehome、海康SDK、大华SDK、华为SDK、宇视SDK、乐橙SDK、萤石云SDK等&#xff0c;并能对外分发RTMP、…

Pseudo-Label : The Simple and Efficient Semi-Supervised Learning Method--论文笔记

论文笔记 资料 1.代码地址 https://github.com/iBelieveCJM/pseudo_label-pytorch 2.论文地址 3.数据集地址 论文摘要的翻译 本文提出了一种简单有效的深度神经网络半监督学习方法。基本上&#xff0c;所提出的网络是以有监督的方式同时使用标记数据和未标记数据来训练的…

加法器的基本操作

基本单元 与门(AND) 全1为1&#xff0c;有0为0 或门(OR) 全0为0&#xff0c;有1为1 非门(NOT) 为1则0&#xff0c;为0则1 异或门(XOR) 两个输入端&#xff0c;相同为0&#xff0c;不同为1 与非门(NADD) 全1为0&#xff0c;有0为1 或非门(NOR) 全0为1&#xff0c;有1为0。刚…

redis 如何使用 scan, go语言

建议用方案乙 文章目录 场景方案方案甲方案乙 拓展 场景 redis 中存在大量 key。 其中有一部分是用户登陆的 session_id&#xff0c; 结构是 &#xff1a; session_id:1session_id:2session_id:3需求&#xff1a; 有多少用户在线 方案 方案甲 keys session_id:*这种方式简…

第一次面试的经历(java开发实习生)

面试官的问题 我想问一下你这边有做过什么项目吗?你方便讲一下你做过的那些项目吗&#xff0c;用了什么技术栈&#xff0c;包括你负责开发的内容是什么&#xff1f;&#xff08;项目经验&#xff09;八大基本数据类型是什么&#xff1f;&#xff08;基础&#xff09;你说一下…

安徽医学期刊

《安徽医学》&#xff08;月刊&#xff09; 主管单位&#xff1a;安徽省卫生健康委员会 主办单位&#xff1a;安徽省医学情报研究所 承办单位&#xff1a;安徽省医学会 收录情况&#xff1a;中国科技论文统计源期刊&#xff08;中国科技核心期刊&#xff09;、中国核心期刊&…

项目部署_持续集成_Jenkins

1 今日内容介绍 1.1 什么是持续集成 持续集成&#xff08; Continuous integration &#xff0c; 简称 CI &#xff09;指的是&#xff0c;频繁地&#xff08;一天多次&#xff09;将代码集成到主干 持续集成的组成要素 一个自动构建过程&#xff0c; 从检出代码、 编译构建…