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

简介: 在下面的这篇文章中,将讲解堆的基础知识,并手动地用 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天前
|
前端开发 JavaScript 安全
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第27天】本文介绍了HTTP/2和HTTPS在前端性能调优中的应用。通过多路复用、服务器推送和头部压缩等特性,HTTP/2显著提升了Web性能。同时,HTTPS确保了数据传输的安全性。文章提供了示例代码,展示了如何使用Node.js创建一个HTTP/2服务器。
35 3
|
4天前
|
前端开发
结合具体案例分析Gitflow分支策略在大型前端项目中的应用优势
通过这个具体案例可以看出,Gitflow 分支策略在大型前端项目中能够提供有条不紊的开发环境,保障项目的稳定性和持续发展。
|
21天前
|
Rust 前端开发 JavaScript
前端性能革命:WebAssembly在高性能计算中的应用探索
【10月更文挑战第26天】随着Web应用功能的日益复杂,传统JavaScript解释执行模式逐渐成为性能瓶颈。WebAssembly(Wasm)应运而生,作为一种二进制代码格式,支持C/C++、Rust等语言编写的代码在浏览器中高效运行。Wasm不仅提升了应用的执行速度,还具备跨平台兼容性和安全性,显著改善了Web应用的响应速度和用户体验。
32 4
|
20天前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
37 2
|
21天前
|
前端开发 安全 应用服务中间件
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第26天】随着互联网的快速发展,前端性能调优成为开发者的重要任务。本文探讨了HTTP/2与HTTPS在前端性能优化中的应用,介绍了二进制分帧、多路复用和服务器推送等特性,并通过Nginx配置示例展示了如何启用HTTP/2和HTTPS,以提升Web应用的性能和安全性。
21 3
|
21天前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
31 2
|
21天前
|
前端开发 JavaScript API
前端框架新探索:Svelte在构建高性能Web应用中的优势
【10月更文挑战第26天】近年来,前端技术飞速发展,Svelte凭借独特的编译时优化和简洁的API设计,成为构建高性能Web应用的优选。本文介绍Svelte的特点和优势,包括编译而非虚拟DOM、组件化开发、状态管理及响应式更新机制,并通过示例代码展示其使用方法。
36 2
|
22天前
|
前端开发 JavaScript 开发者
“揭秘React Hooks的神秘面纱:如何掌握这些改变游戏规则的超能力以打造无敌前端应用”
【10月更文挑战第25天】React Hooks 自 2018 年推出以来,已成为 React 功能组件的重要组成部分。本文全面解析了 React Hooks 的核心概念,包括 `useState` 和 `useEffect` 的使用方法,并提供了最佳实践,如避免过度使用 Hooks、保持 Hooks 调用顺序一致、使用 `useReducer` 管理复杂状态逻辑、自定义 Hooks 封装复用逻辑等,帮助开发者更高效地使用 Hooks,构建健壮且易于维护的 React 应用。
29 2
|
27天前
|
JavaScript 前端开发 测试技术
前端全栈之路Deno篇(五):如何快速创建 WebSocket 服务端应用 + 客户端应用 - 可能是2025最佳的Websocket全栈实时应用框架
本文介绍了如何使用Deno 2.0快速构建WebSocket全栈应用,包括服务端和客户端的创建。通过一个简单的代码示例,展示了Deno在WebSocket实现中的便捷与强大,无需额外依赖,即可轻松搭建具备基本功能的WebSocket应用。Deno 2.0被认为是最佳的WebSocket全栈应用JS运行时,适合全栈开发者学习和使用。
|
23天前
|
前端开发 API UED
深入理解微前端架构:构建灵活、高效的前端应用
【10月更文挑战第23天】微前端架构是一种将前端应用分解为多个小型、独立、可复用的服务的方法。每个服务独立开发和部署,但共同提供一致的用户体验。本文探讨了微前端架构的核心概念、优势及实施方法,包括定义服务边界、建立通信机制、共享UI组件库和版本控制等。通过实际案例和职业心得,帮助读者更好地理解和应用微前端架构。
下一篇
无影云桌面