最小堆最大堆了解吗?一文了解堆在前端中的应用(二)

简介: 在下面的这篇文章中,将讲解堆的基础知识,并手动地用 js 来构建一个最小堆,同时剖析几道经典的 leetcode 算法题。

🐤五、leetcode经典题目剖析


接下来我们引用几道经典的 leetcode 算法,来巩固树和二叉树的知识。


1. leetcode215数组中的第K个最大元素(中等)

(1)题意

附上题目链接:leetcode215数组中的第K个最大元素

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

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

输入输出示例:

  • 输入: [3,2,1,5,6,4]k = 2
  • 输出: 5

(2)解题思路

  • 看到“第K个最大元素”。
  • 考虑选择使用最小堆。

(3)解题步骤

  • 构建一个最小堆,并以此把数组的值插入堆中。
  • 当堆的容量超过K,就删除堆顶。
  • 插入结束后,堆顶就是第K个最大元素。

(4)代码实现

依据上面我们构建的最小堆,接下来,我们用这个最小堆,来完成这道题。具体代码如下:

class MinHeap{
    constructor(){
        this.heap = [];
    }
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        // return (i - 1) >> 1;
    }
    getLeftIndex(i){
        return i*2 + 1;
    }
    getRightIndex(i){
        return i*2 + 2;
    }
    shiftUp(index){
        if(index === 0){
            return;
        }
        const parentIndex = this.getParentIndex(index);
        if(this.heap[parentIndex] > this.heap[index]){
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    shiftDown(index){
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if(this.heap[leftIndex] < this.heap[index]){
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if(this.heap[rightIndex] < this.heap[index]){
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    insert(value){
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }
    pop(){
        this.heap[0] = this.heap.pop();
        this.shiftDown(0);
    }
    peek(){
        return this.heap[0];
    }
    size(){
        return this.heap.length;
    }
}
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
let findKthLargest = function(nums, k){
    const h = new MinHeap();
    nums.forEach(n => {
        h.insert(n);
        if(h.size() > k){
            h.pop();
        }
    });
    return h.peek();
}
console.log(findKthLargest([3,2,1,5,6,4],2)); // 5
复制代码


2. leetcode347前K个高频元素(中等)

(1)题意

附上题目链接:leetcode347前K个高频元素

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

输入输出示例:

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

(2)解题思路

  • 字典解法:将字典转换为数组,且堆数组进行排序;
  • 堆解法:构建一个最小堆,利用字典的键值关系,来记录元素出现的频率。

(3)代码实现

这道题我们用两种做法来实现,一种是字典解法,另外一种是堆解法具体如下:

1)字典解法:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
// 字典解法
let topKFrequent1 = function(nums, k) {
    //定义一个数组
    const map = new Map();
    //先将数组中的元素存放到字典中
    nums.forEach(n => {
        map.set(n, map.has(n) ? map.get(n) + 1 : 1 );
    });
    // 将字典转换为数组,且对数组进行排序
    // 对数组中的第二项进行降序(从大到小)排序,从大到小
    const list = Array.from(map).sort((a, b) => b[1] - a[1]);
    //使用map()方法,创建一个新数组,来存放前k个元素
    return list.slice(0, k).map(n => n[0]);
};
console.log(topKFrequent1([1, 1, 1, 2, 2, 3], 2)); // [1, 2]
复制代码

2)堆解法:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
// 堆解法
class MinHeap{
    constructor(){
        this.heap = [];
    }
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        // return (i - 1) >> 1;
    }
    getLeftIndex(i){
        return i*2 + 1;
    }
    getRightIndex(i){
        return i*2 + 2;
    }
    shiftUp(index){
        if(index === 0){
            return;
        }
        const parentIndex = this.getParentIndex(index);
        if(this.heap[parentIndex] && this.heap[parentIndex].value > this.heap[index].value){
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    shiftDown(index){
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if(this.heap[leftIndex] && this.heap[leftIndex].value < this.heap[index].value){
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if(this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value){
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    insert(value){
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }
    pop(){
        this.heap[0] = this.heap.pop();
        this.shiftDown(0);
    }
    peek(){
        return this.heap[0];
    }
    size(){
        return this.heap.length;
    }
}
let topKFrequent2 = function(nums, k) {
    //初始化一个字典
    const map = new Map();
    //对数组挨个进行遍历,并记录出现次数
    nums.forEach(n => {
        map.set(n, map.has(n) ? map.get(n) + 1 : 1 );
    });
    //实例化一个最小堆
    const h = new MinHeap();
    //对字典中的所有键值对进行遍历
    map.forEach((value, key) => {
        //每遍历一个,堆中就插入一个
        h.insert({value, key});
        //判断当前堆的大小是否大于k值
        if(h.size() > k){
            h.pop();
        }
    });
    //返回值,对字典进行遍历,得到遍历后的键即为结果;
    //并通过map()方法创建一个新数组,才存放具体的值。
    return h.heap.map(a => a.key);
};
console.log(topKFrequent2([1, 1, 1, 2, 2, 3], 2)); // [2, 1]
复制代码


3. leetcode23合并K个排序链表(困难)

(1)题意

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

输入输出示例:

  • 输入: lists = [[1,4,5],[1,3,4],[2,6]]
  • 输出: [1,1,2,3,4,4,5,6]
  • 解释
链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
复制代码

(2)解题思路

  • 新链表的下一个节点一定是k个链表头中的最小节点。
  • 考虑选择使用最小堆。

(3)解题步骤

  • 构建一个最小堆,并以此把链表头插入堆中。
  • 弹出堆顶接到输出链表,并将堆顶所在链表的新链表头插入堆中。
  • 等堆元素全部弹出,合并工作就完成了。

(4)代码实现

class MinHeap{
    constructor(){
        this.heap = [];
    }
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        // return (i - 1) >> 1;
    }
    getLeftIndex(i){
        return i*2 + 1;
    }
    getRightIndex(i){
        return i*2 + 2;
    }
    shiftUp(index){
        if(index === 0){
            return;
        }
        const parentIndex = this.getParentIndex(index);
        if(this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val){
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    shiftDown(index){
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if(this.heap[leftIndex] && this.heap[leftIndex].val < this.heap[index].val){
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if(this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[index].val){
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    insert(val){
        this.heap.push(val);
        this.shiftUp(this.heap.length - 1);
    }
    pop(){
        // 如果堆只有一个元素,直接返回结果
        if(this.size() === 1){
            return this.heap.shift();
        }
        const top = this.heap[0];
        this.heap[0] = this.heap.pop();
        this.shiftDown(0);
        return top;
    }
    peek(){
        return this.heap[0];
    }
    size(){
        return this.heap.length;
    }
}
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
var mergeKLists = function(lists) {
    //实例化一个空链表
    const res = new ListNode(0);
    //定义一个p指针,指向空链表
    let p = res;
    //实例化一个最小堆
    const h = new MinHeap();
    //将题目给的链表,挨个进行遍历
    lists.forEach(l => {
        //遍历后的链表如果不为空,则插入最小堆当中
        if(l){
            h.insert(l);
        }
    });
  //判断堆中是否有内容
    while(h.size()){
        //删除并返回堆顶
        const n = h.pop();
        //让p指针的next节点指向堆顶元素
        p.next = n;
        //p.next的值赋给p指针
        p = p.next;
        //如果堆顶元素有下一个节点,则将其插入堆中
        if(n.next){
            h.insert(n.next);
        }
    }
    return res.next;
};
复制代码


🐪六、结束语


学完这个数据结构的时候,想到上回看面经时有一道算法题。那道题目问到说,假设现在有一个文件,里面有很多个单词,请找出前10个出现频率最高的词汇。

当时我的心里想的是:遍历?但其实今天学完这个数据结构以后,回想起来,这道题的做法就是用最小堆来实现。

所以,堆在日常的应用中都是相通的,只要明白了其中的思想,间接地,也将可以应用到对应的各种场景当中。

到这里,关于堆在前端中的应用的讲解就结束啦!希望对大家有帮助~

如文章有误或有不理解的地方,欢迎小伙伴们评论区留言撒💬



相关文章
|
20天前
|
机器学习/深度学习 存储 前端开发
实战揭秘:如何借助TensorFlow.js的强大力量,轻松将高效能的机器学习模型无缝集成到Web浏览器中,从而打造智能化的前端应用并优化用户体验
【8月更文挑战第31天】将机器学习模型集成到Web应用中,可让用户在浏览器内体验智能化功能。TensorFlow.js作为在客户端浏览器中运行的库,提供了强大支持。本文通过问答形式详细介绍如何使用TensorFlow.js将机器学习模型带入Web浏览器,并通过具体示例代码展示最佳实践。首先,需在HTML文件中引入TensorFlow.js库;接着,可通过加载预训练模型如MobileNet实现图像分类;然后,编写代码处理图像识别并显示结果;此外,还介绍了如何训练自定义模型及优化模型性能的方法,包括模型量化、剪枝和压缩等。
27 1
|
20天前
|
开发者 存储 API
Xamarin 开发者的社区资源概览:从官方文档到GitHub示例,全面探索提升开发技能与解决问题的多元化渠道与实用工具
【8月更文挑战第31天】Xamarin 开发者社区资源概览旨在提升开发效率与解决问题,涵盖官方文档、社区论坛、GitHub 项目等。官方文档详尽,涵盖 Xamarin.Forms 使用、性能优化等;社区论坛供交流心得;GitHub 提供示例代码。此外,第三方博客、视频教程及 Xamarin University 等资源也丰富多样,适合各阶段开发者学习与提升。通过综合利用这些资源,开发者可不断进步,应对技术挑战。
32 0
|
20天前
|
前端开发 Java UED
JSF 面向组件开发究竟藏着何种奥秘?带你探寻可复用 UI 组件设计的神秘之路
【8月更文挑战第31天】在现代软件开发中,高效与可维护性至关重要。JavaServer Faces(JSF)框架通过其面向组件的开发模式,提供了构建复杂用户界面的强大工具,特别适用于设计可复用的 UI 组件。通过合理设计组件的功能与外观,可以显著提高开发效率并降低维护成本。本文以一个具体的 `MessageComponent` 示例展示了如何创建可复用的 JSF 组件,并介绍了如何在 JSF 页面中使用这些组件。结合其他技术如 PrimeFaces 和 Bootstrap,可以进一步丰富组件库,提升用户体验。
34 0
|
20天前
|
开发者 自然语言处理 存储
语言不再是壁垒:掌握 JSF 国际化技巧,轻松构建多语言支持的 Web 应用
【8月更文挑战第31天】JavaServer Faces (JSF) 框架提供了强大的国际化 (I18N) 和本地化 (L10N) 支持,使开发者能轻松添加多语言功能。本文通过具体案例展示如何在 JSF 应用中实现多语言支持,包括创建项目、配置语言资源文件 (`messages_xx.properties`)、设置 `web.xml`、编写 Managed Bean (`LanguageBean`) 处理语言选择,以及使用 Facelets 页面 (`index.xhtml`) 显示多语言消息。通过这些步骤,你将学会如何配置 JSF 环境、编写语言资源文件,并实现动态语言切换。
21 0
|
20天前
|
iOS开发 Android开发 MacOS
从零到全能开发者:解锁Uno Platform,一键跨越多平台应用开发的神奇之旅,让你的代码飞遍Windows、iOS、Android、macOS及Web,技术小白也能秒变跨平台大神!
【8月更文挑战第31天】从零开始,踏上使用Uno Platform开发跨平台应用的旅程。只需编写一次代码,即可轻松部署到Windows、iOS、macOS、Android及Web(通过WASM)等多个平台。Uno Platform为.NET生态带来前所未有的灵活性和效率,简化跨平台开发。首先确保安装了Visual Studio或VS Code及.NET SDK,然后选择合适的项目模板创建新项目。项目结构类似传统.NET MAUI或WPF项目,包含核心NuGet包。通过简单的按钮示例,你可以快速上手并构建应用。Uno Platform让你的技术探索之旅充满无限可能。
23 0
|
20天前
|
前端开发 JavaScript 开发者
JSF与WebSockets,打造实时通信魔法!让你的Web应用秒变聊天室,用户体验飞升!
【8月更文挑战第31天】在现代Web应用开发中,实时通信对于提升用户体验至关重要。本文探讨了如何在主要面向Web应用开发的JSF(JavaServer Faces)框架中引入WebSockets支持,以实现客户端与服务器之间的全双工通信。通过具体示例展示了在JSF应用中实现WebSockets的基本步骤:添加依赖、创建服务器端点以及在前端页面中嵌入JavaScript客户端代码。尽管这一过程中可能会遇到一些挑战,如复杂代码编写和额外配置需求,但借助AWS等云服务平台,开发者仍能高效地完成部署和管理工作,从而增强Web应用的实时通信能力。
28 0
|
20天前
|
API UED 开发者
如何在Uno Platform中轻松实现流畅动画效果——从基础到优化,全方位打造用户友好的动态交互体验!
【8月更文挑战第31天】在开发跨平台应用时,确保用户界面流畅且具吸引力至关重要。Uno Platform 作为多端统一的开发框架,不仅支持跨系统应用开发,还能通过优化实现流畅动画,增强用户体验。本文探讨了Uno Platform中实现流畅动画的多个方面,包括动画基础、性能优化、实践技巧及问题排查,帮助开发者掌握具体优化策略,提升应用质量与用户满意度。通过合理利用故事板、减少布局复杂性、使用硬件加速等技术,结合异步方法与预设缓存技巧,开发者能够创建美观且流畅的动画效果。
43 0
|
20天前
|
前端开发 JavaScript 开发者
Angular状态管理神器ngrx Store:从零开始的实践指南与进阶优化秘籍,让你的前端应用状态井井有条、高效运行的绝招大揭秘
【8月更文挑战第31天】状态管理在现代Web应用开发中至关重要,特别是在构建大型、复杂的Angular应用时。ngrx Store借鉴Redux的设计理念,提供集中式状态管理和可预测的数据流,有助于增强应用的可维护性和可测试性。
7 0
|
20天前
|
前端开发 UED 开发者
React组件优化全攻略:深度解析让你的前端应用飞速运行的秘诀——从PureComponent到React.memo的彻底性能比较
【8月更文挑战第31天】在构建现代Web应用时,性能是提升用户体验的关键因素。React作为主流前端库,其组件优化尤为重要。本文深入探讨了React组件优化策略,包括使用`PureComponent`、`React.memo`及避免不必要的渲染等方法,帮助开发者显著提升应用性能。通过实践案例对比优化前后效果,不仅提高了页面渲染速度,还增强了用户体验。优化React组件是每个开发者必须关注的重点。
34 0
|
20天前
|
前端开发 JavaScript 安全
【前端开发新境界】React TypeScript融合之路:从零起步构建类型安全的React应用,全面提升代码质量和开发效率的实战指南!
【8月更文挑战第31天】《React TypeScript融合之路:类型安全的React应用开发》是一篇详细教程,介绍如何结合TypeScript提升React应用的可读性和健壮性。从环境搭建、基础语法到类型化组件、状态管理及Hooks使用,逐步展示TypeScript在复杂前端项目中的优势。适合各水平开发者学习,助力构建高质量应用。
34 0