简介
堆是一种特殊的完全二叉树。
当堆中的所有的节点大于等于他的子节点,我们可以称这个堆为最大堆,相反,当堆中的所有的节点小于等于他的子节点,我们称这个堆为最小堆。
JS中的堆
Javascript通常用数组来表示堆
堆的节点位置遵循以下规则:
• 左侧子节点的位置是当前节点索引 * 2 + 1,即2 * index + 1
• 右侧子节点的位置是当前节点索引 * 2 + 1,即2 * index + 2
• 父节点的位置(index - 1)/ 2
Javascript实现:最小堆类
实现步骤:
• 在类里,声明一个数组,用来装元素
• 主要方法:插入、删除堆顶、获取堆顶、获取堆大小
插入:
• 将值插入堆的底部,即数组的尾部
• 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值(因为最小堆的父节点小于子节点)
• 大小为K的堆中插入元素的时间复杂度为O(logk)
删除堆顶:
• 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)
• 然后下移:将新堆顶和它的子节点进行交换,知道子节点大于等于这个新的堆顶
• 大小为k的堆中删除堆顶的时间复杂度为O(logk)
获取堆顶元素和堆的大小
• 返回头部元素
• 返回数组长度
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) // 二进制右移一位就是除2的商 return (i - 1) >> 1 } getLeftIndex(i) { return 2 * i + 1 } getRightIndex(i) { return 2 * i + 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 }}
以下LeetCode题都可以用MinHeap类求出。
LeetCode 215.数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4输出: 4
/** * @param {number[]} nums * @param {number} k * @return {number} */var findKthLargest = function(nums, k) { const h = new MinHeap() nums.forEach(n => { h.insert(n) if (h.size() > k) { h.pop() } }) return h.peek()};
LeetCode 347.前K个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2输出: [1,2]
示例 2:
输入: nums = [1], k = 1输出: [1]
解题思路:
• 由于这题需要返回的是出现频率前K高的元素,所以需要多两步操作
• 通过map记录每个元素出现的频数
• 调整MinHeap的比较条件
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 (i - 1) >> 1 } getLeftIndex(i) { return 2 * i + 1 } getRightIndex(i) { return 2 * i + 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) } size() { return this.heap.length }}/** * @param {number[]} nums * @param {number} k * @return {number[]} */// 时间复杂度O(nlogk) 空间复杂度O(n)var topKFrequent = 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 }) if (h.size() > k) { h.pop() } }) return h.heap.map(v => v.key)};
LeetCode 23.合并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:
输入:lists = []输出:[]
示例 3:
输入:lists = [[]]输出:[]
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 (i - 1) >> 1 } // 获取左节点 getLeftIndex(i) { return 2 * i + 1 } // 获取右节点 getRightIndex(i) { return 2 * i + 2 } // 进行上移操作,直至堆顶为最小值 shiftUp(index) { if (index == 0) { return } // 父节点索引 const pIndex = this.getParentIndex(index) if (this.heap[pIndex] && this.heap[pIndex].val > this.heap[index].val) { this.swap(pIndex, index) this.shiftUp(pIndex) } } // 下移操作,直至堆顶为最小值 shiftDown(index) { const lIndex = this.getLeftIndex(index) const rIndex = this.getRightIndex(index) if (this.heap[lIndex] && this.heap[lIndex].val < this.heap[index].val) { this.swap(lIndex, index) this.shiftDown(lIndex) } if (this.heap[rIndex] && this.heap[rIndex].val < this.heap[index].val) { this.swap(rIndex, index) this.shiftDown(rIndex) } } // 插入 insert(value) { this.heap.push(value) 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 h = new MinHeap() const res = new ListNode(0) let p = res lists.forEach(l => { if (l) h.insert(l); }) while(h.size()) { const n = h.pop() p.next = n p = p.next if (n.next) h.insert(n.next) } return res.next};
LeetCode 703.数据流中的第 K 大元素
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
示例:
输入:["KthLargest", "add", "add", "add", "add", "add"][[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]输出:[null, 4, 5, 5, 8, 8]解释:KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);kthLargest.add(3); // return 4kthLargest.add(5); // return 5kthLargest.add(10); // return 5kthLargest.add(9); // return 8kthLargest.add(4); // return 8
/** * @param {number} k * @param {number[]} nums */var KthLargest = function(k, nums) { this.h = new MinHeap() this.k = k nums.forEach(n => { this.add(n) })};/** * @param {number} val * @return {number} */KthLargest.prototype.add = function(val) { this.h.insert(val) if (this.h.size() > this.k ) { this.h.pop() } return this.h.peek()};/** * Your KthLargest object will be instantiated and called as such: * var obj = new KthLargest(k, nums) * var param_1 = obj.add(val) */