leetcode刷题(javaScript)——堆相关场景题总结

堆是什么?堆都能用树表示,并且一般树的实现都是利用链表。平时使用的最多的是二叉堆,它可以用完全二叉树表示,二叉堆易于存储,并且便于索引。在堆的实现时注意:因为是数组,所以父子节点的关系就不需要特殊的结构去维护了,索引之前通过计算就可以得到,省掉了很多麻烦,如果是链表结构,就会复杂很多。

在JavaScript刷题中,堆(Heap)通常用于解决一些需要高效处理优先级的问题,例如找出最大或最小的K个元素、实现优先队列等。堆在刷题中的应用场景包括但不限于以下几个方面:

  1. 找出最大或最小的K个元素:通过维护一个大小为K的最大堆或最小堆,可以快速找出数组中最大或最小的K个元素。

  2. 合并K个有序数组:可以使用堆来合并K个有序数组,实现高效的合并操作。

  3. 实现优先队列:堆可以用于实现优先队列,保证队列中优先级高的元素先出队。

 数组存储二叉堆

        二叉堆是一种完全二叉树,分为最小堆和最大堆两种类型。用数组存储二叉堆, 完全二叉树要求叶子节点从左往右填满,才能开始填充下一层。

  1. 二叉堆:二叉堆是一种完全二叉树,通常使用数组来表示。在最小堆中,父节点的值小于或等于其子节点的值;在最大堆中,父节点的值大于或等于其子节点的值。

  2. 最小堆:在最小堆中,父节点的值小于或等于其子节点的值。根节点是堆中的最小值。

  3. 最大堆:在最大堆中,父节点的值大于或等于其子节点的值。根节点是堆中的最大值。

注:完全二叉树和满二叉树不同,完全二叉树允许叶子节点不铺满。

 

同一组数据最小堆和最大堆是唯一的吗?

同一组数据的最小堆或最大堆不是唯一的。以最小堆为例:最小堆是一种特殊的二叉堆,它满足以下两个性质:

  1. 父节点的值小于或等于其子节点的值。
  2. 堆中任意节点的子树也是一个最小堆。

        由于最小堆只要满足上述性质即可,因此对于同一组数据,可以有多种不同的最小堆表示方式。这是因为在构建最小堆的过程中,可以选择不同的节点作为根节点,从而得到不同的堆结构。

所以,同一组数据的最小堆并不是唯一的,可以有多种不同的表示方式。

如何找到节点i父节点和子节点呢?

        二叉堆在数组是按层次遍历进行存储的,从上至下,从左至右。因此子节点的index要大于父节点的index。在二叉堆中,可以通过以下方式计算父节点和子节点的索引:

  • 父节点索引计算:对于节点i,其父节点的索引为(i-1)/2。
  • 左子节点索引计算:对于节点i,其左子节点的索引为2*i+1。
  • 右子节点索引计算:对于节点i,其右子节点的索引为2*i+2。

如何删除节点?

在删除一个元素之后,整体往前移动是比较费时的,这也是随机存储结构的短板。因此,堆在删除元素的时候是把最后一个叶子节点补充到树根节点。在通过节点移动将堆调整为最小堆或最大堆。

最小堆代码示例

创建堆,往堆里新增元素,删除堆顶,获取堆的父节点下标,获取堆左右子节点下标

class MinHeap {constructor() {this.heap = [];}getParentIndex(index) {return Math.floor((index - 1) / 2);}getLeftIndex(index) {return index * 2 + 1;}getRightIndex(index) {return index * 2 + 2;}swap(i1, i2) {const temp = this.heap[i1];this.heap[i1] = this.heap[i2];this.heap[i2] = temp;}//往堆最后添加节点,触发元素上移//当前元素与其跟节点进行比较如果大于其跟节点与根节点进行交换,重复操作up(index) {//如果是0就不移动if (index == 0) return;//获取父元素const parentIndex = this.getParentIndex(index);if (this.heap[parentIndex] > this.heap[index]) {this.swap(parentIndex, index);//对交换后parentIndex继续向上递归this.up(parentIndex);}}//从堆顶删除元素时,将子节点移到堆顶,触发元素下移down(index) {const leftIndex = this.getLeftIndex(index);const rightIndex = this.getRightIndex(index);//左子树小于根节点,交换左子树与根if (this.heap[leftIndex] < this.heap[index]) {this.swap(leftIndex, index);this.down(leftIndex);}//同理,右子树小于根节点,交换右子树与根if (this.heap[rightIndex] < this.heap[index]) {this.swap(rightIndex, index);this.down(rightIndex);}}//往堆里增加元素insert(value) {this.heap.push(value);this.up(this.heap.length - 1);}//将堆顶元素弹出pop() {this.heap[0] = this.heap.pop();this.down(0);}//获取堆顶peek() {return this.heap[0];}//获取堆大小size() {return this.heap.length;}
}

如何通过js构建最大堆

思路跟最小堆构建一样,只不过是父元素永远比左右子树节点大

首先构造器得有吧,创建一个空的heap数组;

其次将获取父节点index、左右子节点index、堆大小size、栈顶元素获取、两节点交换这些简单的辅助函数写一下;

主函数insert插入节点:每次从heap的尾部也就是最后一个叶子节点插入,插进去的叶子节点得向上找它的位置吧,写一个up方法,找到插入节点位置;

up方法实现:找到节点的父元素,看父元素是否比自己小,如果小的换就交换,并且递归up方法,直到所有元素都调整好。

删除节点pop方法:这里依然将最后节点移到第一个跟节点,此时根节点一定是最小的,不符合最大堆规则,所以调整跟节点向下移动,写一个down方法移动根节点。这里注意堆的size如果等于1直接删除而不用移动节点。

down方法实现:找到根的左右子节点,选择比根大的节点进行交换,并递归down方法,直到所有节点都调整好。

交换节点swap可以使用数组解构进行交换,而不用单独定义中间值。

class MaxHeap {constructor() {this.heap = [];}//获取父元素下标getParentIndex(index) {return Math.floor((index - 1) / 2);}//获取左子树下标getLeftIndex(index) {return 2 * index + 1;}//获取右子树下标getRightIndex(index) {return 2 * index + 2;}//获取堆大小size() {return this.heap.length;}//获取堆顶元素peek() {return this.heap[0];}//交换节点swap(id1, id2) {[this.heap[id2], this.heap[id1]] = [this.heap[id1], this.heap[id2]];}//插入节点insert(value) {this.heap.push(value);this.up(this.size() - 1);}//删除节点pop() {let last = this.heap.pop();if (this.size() === 0) return;this.heap[0] = last;this.down(0);}//向上移动节点up(index) {const parentIndex = this.getParentIndex(index);if (this.heap[index] > this.heap[parentIndex]) {this.swap(parentIndex, index);this.up(parentIndex);}}//向下移动节点down(index) {const leftIndex = this.getLeftIndex(index);const rightIndex = this.getRightIndex(index);if (leftIndex < this.size() &&this.heap[leftIndex] > this.heap[index]) {this.swap(leftIndex, index);this.down(leftIndex);}if (rightIndex < this.size() &&this.heap[rightIndex] > this.heap[index]) {this.swap(rightIndex, index);this.down(rightIndex);}}
}

 215. 数组中的第K个最大元素

解决数组中的第K个最大元素问题时,通常使用最小堆来实现。通过维护一个大小为K的最小堆,可以在O(NlogK)的时间复杂度内找到数组中的第K个最大元素。

但是题目最近新增了一个要求:就是必须让算法的时间复杂度控制在O(n),使用堆超出了时间复杂度,因此这里仅供参考。具体实现这个题目还是得用快排。

使用最小堆获取数组中第K个最大元素的思路:

  1. 初始化一个大小为K的最小堆:将数组中的前K个元素放入最小堆中。

  2. 遍历数组剩余元素:从第K+1个元素开始遍历数组,对于每个元素,如果大于最小堆的堆顶元素(堆中最小的元素),则将该元素加入最小堆,并移除堆顶元素。

  3. 返回堆顶元素:遍历完成后,最小堆的堆顶元素即为数组中的第K个最大元素。

使用最小堆的优势在于可以保持堆的大小为K,只需维护K个元素,避免了对整个数组进行排序或维护大堆的复杂度。

/*** @param {number[]} nums* @param {number} k* @return {number}*/
var findKthLargest = function (nums, k) {let minHeap = new MinHeap();nums.forEach(item => {minHeap.insert(item);if (minHeap.size() > k) {//每次pop的是栈顶,及k+1个元素中最小的minHeap.pop();}})return minHeap.peek();
};class MinHeap {constructor() {this.heap = [];}getParentIndex(index) {return Math.floor((index - 1) / 2);}getLeftIndex(index) {return index * 2 + 1;}getRightIndex(index) {return index * 2 + 2;}swap(i1, i2) {const temp = this.heap[i1];this.heap[i1] = this.heap[i2];this.heap[i2] = temp;}//往堆最后添加节点,触发元素上移//当前元素与其跟节点进行比较如果大于其跟节点与根节点进行交换,重复操作up(index) {//如果是0就不移动if (index == 0) return;//获取父元素const parentIndex = this.getParentIndex(index);if (this.heap[parentIndex] > this.heap[index]) {this.swap(parentIndex, index);//对交换后parentIndex继续向上递归this.up(parentIndex);}}//从堆顶删除元素时,将子节点移到堆顶,触发元素下移down(index) {const leftIndex = this.getLeftIndex(index);const rightIndex = this.getRightIndex(index);//左子树小于根节点,交换左子树与根if (this.heap[leftIndex] < this.heap[index]) {this.swap(leftIndex, index);this.down(leftIndex);}if (this.heap[rightIndex] < this.heap[index]) {//同理,右子树小于根节点,交换右子树与根this.swap(rightIndex, index);this.down(rightIndex);}}//往堆里增加元素insert(value) {this.heap.push(value);this.up(this.heap.length - 1);}//将堆顶元素弹出pop() {this.heap[0] = this.heap.pop();this.down(0);}//获取堆顶peek() {return this.heap[0];}//获取堆大小size() {return this.heap.length;}
}

 算法可以通过37/41个测试用例,这里仅提供一个最小堆解决的思路

 703. 数据流中的第 K 大元素

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

  • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
  • int add(int val)val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

 

 思路:也是用最小堆来实现,最小堆前面已经介绍过堆的插入删除,直接将创建堆对象的类拿来用即可。

动态构建一个长度为k的最小堆,对顶即第k大的的元素。因为比k小的元素都被pop出去了

/*** @param {number} k* @param {number[]} nums*/
var KthLargest = function (k, nums) {//构建一个k个长度的最小堆this.k = k;this.minHeap = new MinHeap();// 初始化最小堆for (let num of nums) {this.add(num);}
};/** * @param {number} val* @return {number}*/
KthLargest.prototype.add = function (val) {this.minHeap.insert(val);while (this.minHeap.size() > this.k) {this.minHeap.pop();}return this.minHeap.peek();
};/*** Your KthLargest object will be instantiated and called as such:* var obj = new KthLargest(k, nums)* var param_1 = obj.add(val)*/
class MinHeap {constructor() {this.heap = [];}getParentIndex(index) {return Math.floor((index-1)/2);}getLeftIndex(index) {return index * 2 + 1;}getRightIndex(index) {return index * 2 + 2;}swap(id1, id2) {const temp = this.heap[id1];this.heap[id1] = this.heap[id2];this.heap[id2] = temp;}up(index) {const parentIndex = this.getParentIndex(index);if (this.heap[index] < this.heap[parentIndex]) {this.swap(parentIndex, index);this.up(parentIndex);}}down(index) {const leftIndex = this.getLeftIndex(index);const rightIndex = this.getRightIndex(index);if (leftIndex < this.heap.length && this.heap[leftIndex] < this.heap[index]) {this.swap(leftIndex, index);this.down(leftIndex);}if (rightIndex < this.heap.length && this.heap[rightIndex] < this.heap[index]) {this.swap(rightIndex, index);this.down(rightIndex);}}insert(item) {this.heap.push(item);this.up(this.heap.length - 1);}pop() {if (this.size() == 0) return null;this.heap[0] = this.heap.pop();this.down(0);}peek() {if (this.size() == 0) return null;return this.heap[0];}size() {return this.heap.length;}
}

 1046. 最后一块石头的重量

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

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

相关文章

气膜建筑是由什么材料制成的?PVDF膜材的革新应用值得期待吗?

随着科技的不断进步和发展&#xff0c;建筑行业也在不断涌现新型的建筑材料。气膜建筑作为其中一种创新的建筑膜材&#xff0c;在体育馆、运动场馆、展览厅等场所得到了广泛的应用。那么&#xff0c;究竟是什么材料构成了气膜建筑呢&#xff1f;轻空间小编将为您详细介绍。 气膜…

Python数值方法在工程和科学问题解决中的应用

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 随着计算机技术的不断发展&#xff0c;Python作…

《行业指标体系白皮书》重磅发布,剖析指标建设困境,构建前瞻性的指标体系(附下载)

正处于企业指标建设过程中的你&#xff0c;是否经常遇到这样的问题&#xff1a; • 各个部门独立建设信息系统&#xff0c;由此产生的指标定义和计算方式各异&#xff0c;导致管理层无法快速准确地掌握整体业务运行状况 • 缺乏对指标的统一管理和规范&#xff0c;产生重复的指…

OPCUA 学习笔记:程序模型

无论是边缘控制器&#xff0c;还是PLC 中&#xff0c;除了信息模型之外&#xff0c;还有应用程序&#xff0c;这些程序可能是IEC61131-3 编写的程序&#xff0c;也可能是其它程序开发的可执行程序。 尽管OPCUA 描述模型能力很强&#xff0c;但是它缺乏算法的描述方式。但是OPCU…

欧盟地区 iOS DMA 更新后,Brave浏览器安装量激增

自《欧洲数字市场法案》发布后&#xff0c;苹果公司为遵守该法案&#xff0c;在 iOS 17.4 中引入了一项新功能&#xff0c;要求欧盟用户从包括 Brave 在内的列表中选择一个默认网络浏览器。 用户在安装更新后首次打开 Safari 时&#xff0c;会弹出一则消息提示用户从本国流行的…

【C++教程从0到1入门编程】第九篇:STL中Vector类

一、vector的介绍 1.vector的介绍 vector是表示可变大小数组的序列容器。 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&…

js的异常处理

1、throw抛异常 throw抛出异常信息&#xff0c;程序也会终止执行&#xff1b; throw后面跟的是错误提示信息&#xff1b; new Error() 配个throw使用&#xff0c;能设置更详细的错误信息。 function counter(x,y) {if (!x || !y) {throw new Error(参数不能为空)}retu…

技术上的现货黄金开户可以免一下吗?

现货黄金是国际市场上的电子黄金合约买卖&#xff0c;是一种自由而灵活的投资方式&#xff0c;投资者可以根据自己对行情的判断选择交易的方向&#xff0c;只要成功的抓住了一点市场波动&#xff0c;就可以借助杠杆的作用&#xff0c;把它转化为较大的收益。 现货黄金实行保证金…

利用RPA自动化,批量上传资源一个账号搞了7700+收益!

您好&#xff0c;我是码农飞哥&#xff08;wei1148s&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。&#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通…

日期问题 刷题笔记

思路 枚举 19600101 到20591231这个区间的数 获得年月日 判断是否合法 如果合法 关于题目给出的日期 有三种可能 年/月/日 日/月/年 月/日/年 判断 是否和题目给出的日期符合 如果符合 输出 闰年{ 1.被4整除不被100整除 2.被400整除} 补位写法“%02d" 如果不…

Vue+SpringBoot打造创意工坊双创管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 管理员端2.2 Web 端2.3 移动端 三、系统展示四、核心代码4.1 查询项目4.2 移动端新增团队4.3 查询讲座4.4 讲座收藏4.5 小程序登录 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的创意工坊双创管理…

基于SSM框架的理发店管理系统的设计与实现【附项目源码】分享

基于SSM框架的理发店管理系统的设计与实现&#xff1a; 源码地址&#xff1a;https://download.csdn.net/download/qq_41810183/88842785 理发店管理系统设计与实现需求文档 一、引言 随着信息技术的发展和普及&#xff0c;各行业都在寻求信息化管理以提升服务效率与用户体验…