算法力扣刷题 三十三【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,元素作为value
        for(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;	 //初始化为1
        Dq 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.mfbz.cn/a/780762.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第14天|226.翻转二叉树、101. 对称二叉树、104. 二叉树的最大深度、111.二叉树的最小深度

打卡Day14 1.226.翻转二叉树2.101. 对称二叉树扩展100. 相同的树572. 另一棵树的子树 3.104. 二叉树的最大深度扩展559.n叉树的最大深度 3.111.二叉树的最小深度 1.226.翻转二叉树 题目链接&#xff1a;226.翻转二叉树 文档讲解&#xff1a; 代码随想录 &#xff08;1&#x…

博客的部署方法论

有了域名后&#xff0c;可以方便其他人记住并访问&#xff0c;历史上不乏大企业花大价钱购买域名的&#xff1a; 京东域名换成 JD.com&#xff0c;并且说是为了防止百度吸引流量&#xff0c;为什么&#xff1f;唯品会买下域名 VIP.COM 或花费千万 ‍ 域名提供商 如果想要域…

Xilinx FPGA:vivado关于真双端口的串口传输数据的实验

一、实验内容 用一个真双端RAM&#xff0c;端口A和端口B同时向RAM里写入数据0-99&#xff0c;A端口读出单数并存入单端口RAM1中&#xff0c;B端口读出双数并存入但端口RAM2中&#xff0c;当检测到按键1到来时将RAM1中的单数读出显示到PC端&#xff0c;当检测到按键2到来时&…

普通Java工程如何在代码中引用docker-compose.yml中的environment值

文章目录 一、概述二、常规做法1. 数据库配置分离2. 代码引用配置3. 编写启动类4. 支持打包成可执行包5. 支持可执行包打包成docker镜像6. docker运行 三、存在问题分析四、改进措施1. 包含environment 变量的编排文件2. 修改读取配置文件方式3. 为什么可以这样做 五、运行效果…

股票Level-2行情是什么,应该怎么使用,从哪里获取数据

行情接入方法 level2行情websocket接入方法-CSDN博客 相比传统的股票行情&#xff0c;Level-2行情为投资者打开了更广阔的视野&#xff0c;不仅限于买一卖一的表面数据&#xff0c;而是深入到市场的核心&#xff0c;提供了十档乃至千档的行情信息&#xff08;沪市十档&#…

JavaWeb-【1】HTML

笔记系列持续更新,真正做到详细!!本次系列重点讲解后端,那么第一阶段先讲解前端 目录 1、Javaweb技术体系 2、BS架构说明 3、官方文档 4、网页组成 5、HTML 6、HTML快速入门 7、HTML基本结构 8、HTML标签 ​9、HTML标签使用细节 ①、font标签 ②、字符实体 ③、标…

图神经网络dgl和torch-geometric安装

文章目录 搭建环境dgl的安装torch-geometric安装 在跑论文代码过程中&#xff0c;许多小伙伴们可能会遇到一些和我一样的问题&#xff0c;就是文章所需要的一些库的版本比较老&#xff0c;而新版的环境跑代码会报错&#xff0c;这就需要我们手动的下载whl格式的文件来安装相应的…

SSM中小学生信息管理系统 -计算机毕业设计源码02677

摘要 随着社会的发展和教育的进步&#xff0c;中小学生信息管理系统成为学校管理的重要工具。本论文旨在基于SSM框架&#xff0c;采用Java编程语言和MySQL数据库&#xff0c;设计和开发一套高效、可靠的中小学生信息管理系统。中小学生信息管理系统以学生为中心&#xff0c;通过…

机器学习筑基篇,​Ubuntu 24.04 编译安装 Python 及多版本切换

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] Ubuntu 24.04 编译安装最新Python及多版本切换 描述&#xff1a;说到机器学习&#xff0c;人工智能&#xff0c;深度学习不免会提到Python这一门编程语言&#xff08;人生苦短&#xff0c;及时Pyt…

【MySQL】逻辑架构与存储引擎

一、逻辑架构 1、MySQL逻辑架构 我们可以根据上图来对sql的执行过程进行分析 第一步&#xff1a;客户端与服务器建立一个连接&#xff0c;从连接池中分配一个线程处理SQL语句第二步&#xff1a;SQL接口接受SQL指令第三步&#xff1a;如果是5.7版本&#xff0c;就会先去缓存中…

Facebook数据仓库的变迁与启示

❃博主首页 &#xff1a; <码到三十五> ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a; <搬的每块砖&#xff0c;皆为峰峦之基&#xff1b;公众号搜索(码到…

史上最全的自抗扰控制(ADRC)学习资料

史上最全的自抗扰控制&#xff08;ADRC&#xff09;学习资料 需要的私信我~ 需要的私信我~ 需要的私信我~ ​ 本文将作者近些年来学习ADRC算法的学习资料进行汇总&#xff0c;整理了这一版相对较全的学习资料&#xff0c;包含参考文献以及仿真案例&#xff0c;适合初学者入门&…

STM32实现看门狗(HAL库)

文章目录 一. 看门狗1. 独立看门狗&#xff08;IWDG&#xff09;1.1 原理1.2 相关配置1.3 相关函数 2. 窗口看门狗&#xff08;WWDG&#xff09;2.1 原理2.2 相关配置2.3 相关函数 一. 看门狗 单片机在日常工作中常常会因为用户配置代码出现BUG&#xff0c;而导致芯片无法正常工…

21天学通C++:第九、十章节

第九章&#xff1a;类和对象 带默认值的构造函数参数 注意&#xff1a;默认构造函数是调用时可不提供参数的构造函数&#xff0c;而并不一定是不接受任何参数的构造函数。 因此&#xff0c;下面的构造函数虽然有两个参数&#xff0c;但它们都有默认值&#xff0c;因此也是默认…

CurrentHashMap巧妙利用位运算获取数组指定下标元素

先来了解一下数组对象在堆中的存储形式【数组长度&#xff0c;数组元素类型信息等】 【存放元素对象的空间】 Ma 基础信息实例数据内存填充Mark Word,ClassPointer,数组长度第一个元素第二个元素固定的填充内容 所以我们想要获取某个下标的元素首先要获取这个元素的起始位置…

Java 有什么必看的书?

Java必看经典书有这两本&#xff1a; 1、Java核心技术速学版&#xff08;第3版&#xff09; 经典Java开发基础书CoreJava速学版本&#xff01;Java入门优选书籍&#xff0c;更新至Java17&#xff0c;内容皆是精华&#xff0c;让Java学习更简单&#xff0c;让Java知识应用更快速…

fasttext工具介绍

fastText是由Facebook Research团队于2016年开源的一个词向量计算和文本分类工具。尽管在学术上并未带来巨大创新&#xff0c;但其在实际应用中的表现却非常出色&#xff0c;特别是在文本分类任务中&#xff0c;fastText往往能以浅层网络结构取得与深度网络相媲美的精度&#x…

STM32CubeMX实现4X5矩阵按键(HAL库实现)

为了实现计算器键盘&#xff0c;需要使用4X5矩阵按键&#xff0c;因此&#xff0c;我在4X4矩阵键盘上重新设计了一个4X5矩阵按键。原理图如下&#xff1a; 原理描述&#xff1a; 4X5矩阵按键&#xff0c;可以设置4个引脚为输出&#xff0c;5个引脚为输入模式&#xff0c;4个引…

MPS---MPQ86960芯片layout设计总结

MPQ86960 是一款内置功率 MOSFET 和栅极驱动的单片半桥。它可以在宽输入电压 (VIN) 范围内实现高达 50A 的连续输出电流 (IOUT)&#xff0c;通过集成MOSFET 和驱动可优化死区时间 (DT) 并降低寄生电感&#xff0c;从而实现高效率。 MPQ86960 兼容三态输出控制器&#xff0c;另…