【leetcode100-074/075/076】【堆】三题合集

2024-01-29 06:20

本文主要是介绍【leetcode100-074/075/076】【堆】三题合集,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【降序第k元素】

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

思路:

既然在堆专题,那我们就先用堆来解一下,用STL固然很爽,但这里还是得手撕一下,不然显得这个题很多余。。

  • 首先是建堆过程,以本题需要的大根堆为例:我们从最后一个有孩子的节点开始,检查它和孩子(们)的关系,只要孩子中有比它更大的,就用孩子中更大的那一个和它交换位置,如果发生了交换,则还要继续检查新的以它为根节点的子树是否合法。从后到前依次检查完所有节点后,一个合法的堆就建好了。
  • 然后是取出前k个数的过程,由于我们采用了vector来存储堆,所以可以通过设置下标界限来控制堆的大小,每次我们将堆顶和当前堆内的最后一个元素交换位置,并缩小下标范围,将被换到末尾去的原堆顶元素从堆内取出,并从堆顶开始调整被换上去的节点的位置,直到堆重新合法。将此过程重复k-1次,我们就取出了堆中前k-1大的数,此时留在堆顶的就是原数组中倒序第k个元素了。

不考虑专题的话,其实本题还可以用快速选择来解,也就是快排partition思想的应用。每次partition我们可以确定一个元素的正确位置,然后我们可以通过k和这个正确元素下标的关系,来确定第k大的元素在这个下标的左边部分/右边部分/就是这个数本身。反复进行partition操作直到找到指定下标即可。

//大根堆
class Solution {
public:int findKthLargest(vector<int>& nums, int k) {int n = nums.size();//建堆for (int i = n / 2 - 1; i >= 0; i--) {//调整根节点为i的树int cur = i;while (cur < n / 2) {//有孩子比parent大//只有左孩子if (2 * cur + 2 >= n) {//左孩子比parent大if (nums[2 * cur + 1] > nums[cur]) {swap(nums[cur], nums[2 * cur + 1]);cur = 2 * cur + 1;} elsebreak;}//左右孩子都有else if (nums[cur] < nums[2 * cur + 1] ||nums[cur] < nums[2 * cur + 2]) {//左孩子大if (nums[2 * cur + 1] >= nums[2 * cur + 2]) {swap(nums[cur], nums[2 * cur + 1]);cur = 2 * cur + 1;} else {swap(nums[cur], nums[2 * cur + 2]);cur = 2 * cur + 2;}} else {break;}}}//删k-1次,堆顶就是要找的//树内最大下标int limit = n;for (int i = 1; i < k; i++) {//删:换到末尾,范围--swap(nums[0], nums[limit - 1]);limit--;//从根开始调整int cur = 0;while (cur < limit / 2) {//只有左孩子if (2 * cur + 2 >= limit) {//左孩子比parent大if (nums[2 * cur + 1] > nums[cur]) {swap(nums[cur], nums[2 * cur + 1]);cur = 2 * cur + 1;} elsebreak;}//左右孩子都有else if (nums[cur] < nums[2 * cur + 1] ||nums[cur] < nums[2 * cur + 2]) {//左孩子大if (nums[2 * cur + 1] >= nums[2 * cur + 2]) {swap(nums[cur], nums[2 * cur + 1]);cur = 2 * cur + 1;} else {swap(nums[cur], nums[2 * cur + 2]);cur = 2 * cur + 2;}} else {break;}}}return nums[0];}
};

【前k高频元素】

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

思路:

本题虽然放在堆,但我感觉后面堆排序取出前k个元素的过程是一眼就能想到的思路,反而是前面统计频次并把信息转换成堆可以使用的样子比较难想,可能是因为对数据结构的熟悉程度还不太够。

  • 先来说转换部分:首先统计频次,自然是哈希表了,用一个unordered_map给它存下来。可是哈希表不能自己换顺序,怎么办?其实很简单,unordered_map里面存储的元素就是pair啊,我们用一个vector把这些pair都存起来不就好了,到了vector里面那不是你想咋换咋换呢。好了,到此我们需要的数据就准备好了。
  • 然后就是建个堆,注意比较的时候要用pair的second元素,再手动pop堆顶k次就好了(没错还是手撕,不然感觉白写这个题)。
class Solution {
public://传入下标范围void adjustHeap(vector<pair<int, int>>& elements, int cur, int limit) {//还有孩子while (2 * cur + 1 <= limit) {int lchild = 2 * cur + 1;int rchild = 2 * cur + 2;//只有左孩子if (rchild > limit) {if (elements[cur].second < elements[lchild].second) {swap(elements[cur], elements[lchild]);cur = lchild;} elsebreak;} else { //两个孩子//有至少一个比parent大if (elements[cur].second < elements[lchild].second ||elements[cur].second < elements[rchild].second) {//把更大的换上去if (elements[lchild].second >= elements[rchild].second) {swap(elements[cur], elements[lchild]);cur = lchild;} else {swap(elements[cur], elements[rchild]);cur = rchild;}} elsebreak;}}}vector<int> topKFrequent(vector<int>& nums, int k) {//哈希表统计一遍unordered_map<int, int> hash;for (int a : nums) {hash[a]++;}//存到vectorvector<pair<int, int>> elements(hash.begin(), hash.end());// 1.直接sort+输出// 2.前k个建堆+遍历剩余+输出// 3.整体建堆,输出k次int n = elements.size();for (int i = n / 2 - 1; i >= 0; i--) {adjustHeap(elements, i, n - 1);}vector<int> ans;for (int i = 0; i < k; i++) {ans.push_back(elements[0].first);swap(elements[0], elements[n - i - 1]);adjustHeap(elements, 0, n - i - 2);}return ans;}
};

【数据流中位数】

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

  • 例如 arr = [2,3,4] 的中位数是 3 。
  • 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。

实现 MedianFinder 类:

  • MedianFinder() 初始化 MedianFinder 对象。

  • void addNum(int num) 将数据流中的整数 num 添加到数据结构中。

  • double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。

思路:

一开始被hard定位给吓到了,实际上逻辑很简单,实现细节也不多,感觉甚至还不如二分查找的各种变体麻烦(可恶真的很麻烦),属于只要你知道这个思路从此以后同类型的题都可以屡试不爽的那种类型。

首先我们观察中位数的位置,不管它是一个还是两个,如果我们在第一个的右边切一刀,把一个有序数组切成两半,会发生什么?第一个数一定是左半边所有数里最大的,而如果有第二个数,它一定是右半部分最小的,以及左右两半的元素个数要么相同,要么左边比右边多一个。

有了这三条规则,该做什么就很清楚了。

初始状态我们的数组是空的,随着新数字的到来,我们依次将其按照规则添加到数组中,并实时地计算出添加后左右半边的最大值和最小值,就可以顺利地随时获取中位数啦。

那么如何实时地计算出添加后左右半边的最大值和最小值呢,显然堆很合适干这个事情。

每次拿到一个数,我们先根据当前的中位数判断它应该在左半边or右半边,并把它插入相应的堆+调整好。注意,这时候我们的第三条规则“左右两半的元素个数要么相同,要么左边比右边多一个”是有可能被破坏的,所以针对这个被破坏的情况我们还需要从造成规则破坏的那一边拿一个元素到另一边去,以恢复两边元素数量关系的合法性。这题就不手撕堆了,用priority_queue偷懒一下吧qwq。

class MedianFinder {
public:priority_queue<int, vector<int>, less<int>> left;priority_queue<int, vector<int>, greater<int>> right;MedianFinder() {}void addNum(int num) {//空if (left.empty()) {left.push(num);return;}//偶数插左边if (left.size() == right.size()) {//非空,num应该在左边if (num <= left.top()) {//左堆插入left.push(num);}//非空,num应该在右边else {//左堆插入right[0],右堆right[0]换numright.push(num);left.push(right.top());right.pop();}}//奇数插右边else {// num应该在左边if (num < left.top()) {//右堆插入left[0],左堆插入numleft.push(num);right.push(left.top());left.pop();}// num应该在右边else {//右堆插入right.push(num);}}}double findMedian() {//偶数取平均if (left.size() == right.size()) {return (left.top() + right.top()) / 2.0;}//奇数左根else {return 1.0 * left.top();}}
};

这篇关于【leetcode100-074/075/076】【堆】三题合集的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/655996

相关文章

一加全机型TWRP合集/橙狐recovery下载-20240603更新-支持一加12/Ace3V手机

TWRP是目前安卓平台的刷机神器,可快速刷写第三方ROM或官方系统,刷入TWRP之前需要解锁BL,目前已适配一加多个机型。ROM乐园小编20240603整理,涵盖一加1到一加Ace3V多机型专用TWRP文件,个人机型橙狐recovery适配相对完整,我们也进行了分类收集,帮助更多小伙伴学会玩机刷机。随着AB分区的加入,新的一加刷机门槛偏高,并不是单纯的flash  twrp到recovery分区,

Java 校招面经合集

小博Java面试路上的点点滴滴 篇章小博博客地址Java 面试之基础篇点击前往Java 面试之框架篇点击前往Java 面试之数据结构篇点击前往Java 面试之JVM篇点击前往Java 面试之多线程篇点击前往Java 面试之数据库篇点击前往Java 面试之计算机网络篇点击前往Java 面试之操作系统篇点击前往Java 面试之算法篇点击前往Java 面试之附加篇点击前往 小博Spring Boot

智能体合集

海外版coze: 前端代码助手 后端代码助手:   前端代码助手:

程序员书单合集

1、java学习基础编程篇 下载地址:http://blog.csdn.net/shenzhq1980/article/details/48375543 Java程序设计语言.(美国)阿诺德.清晰版 JAVA2核心技术第1卷.基础知识7thJAVA.2核心技术.卷II:高级特性7thJava语言程序设计-基础篇(原书第8版)Java语言程序设计-进阶篇(原书第8版)Java核心技术 卷II 高

智能猫砂盆效果这么惊艳吗?绝对不踩雷的智能猫砂盆合集来啦

身为一个铲屎官,我深受“天天铲屎”的困扰。想要片刻放松都不行,因为猫砂盆一旦堆积屎尿,尤其在夏天,会迅速发臭,滋生细菌。对猫而言,不清理猫砂盆会让它们感到不适,可能引发疾病或拒绝使用猫砂盆。对人而言,不仅会忍受难闻的气味,还可能接触到有害细菌和病毒,对健康造成威胁。因此,为了猫咪和我们的健康,我选择去借助科技的力量,智能猫砂盆,不得不说科技改变铲屎命运!现在轮到它干活,我解放双手躺平,顺便周末旅个

2024最新AI大模型-LLm八股合集(八)-Transformer模型

更多2024最新AI大模型-LLm八股合集可以拉到文末!!! MHA & MQA & MGA (1)MHA 从多头注意力的结构图中,貌似这个所谓的多个头就是指多组线性变换层,其实并不是,只有使用了一组线性变化层,即三个变换张量对Q,K,V分别进行线性变换,这些变换不会改变原有张量的尺寸,因此每个变换矩阵都是方阵,得到输出结果后,多头的作用才开始显现,每个头开始从词义层面分割输出的张量,也就是

webgl合集-怎么初始化webgl通过initShader函数

不白学就行 1.代码 function initShader(gl, vsSource, fsSource) {function compileShader(type, source) {const shader = gl.createShader(type)gl.shaderSource(shader, source)gl.compileShader(shader)if (!gl

Git常用命令小合集

GIT常用命令  *来自廖雪峰老师的git教程,以下是我的简要笔记,可能有点乱,但不影响阅读   初次运行git: ssh-keygen -t rsa -C youremail@example.com 生成key 配置当前用户名和邮箱 Gitconfig --global user.name “your name” Gitconfig –global user.email “y

业务需求分析师的岗位职责说明(合集)

业务需求分析师的岗位职责说明1   职责:   1、研究并理解客户的战略、商业模式,挖掘并揭示客户的痛点和诉求;   2、引导需求探寻,创建并清楚展示方案蓝图,确保客户和交付团队理解并达成共识;   3、定义关键目标、成功要素,识别风险、挑战、依赖和约束;   4、高效梳理业务需求,负责产品的业务需求分解,流程、功能与交互设计,制作原型,协助项目经理设计商业需求和市场需求分析;

leetcode100相同的树

思路 主要是递归,判断终止条件 代码 public boolean isSameTree(TreeNode p, TreeNode q) {if (p == null && q == null){//全为空return true;}if (p==null||q==null){ //有一个nullreturn false;}if (p.val != q.val){return false;}r