堆的概念

  • 堆是一种特殊的完全二叉树
  • 所有节点都大于等于(最大堆)或小于等于(最小堆)它的子节点

image.png

  • JS中常用数组表示堆
  • 广度优先遍历填到数组中
  • 任意节点的左侧子节点的位置是2 * index + 1
  • 任意节点的右侧子节点的位置是2 * index + 2
  • 任意节点的父节点的位置是(index - 1) / 2

image.png

应用

  • 堆能高效快速的找出最大值和最小值,时间复杂度:O(1)
  • 找出第K个最大(小)元素

image.png

第K个最大(小)元素

  • 构建一个最小(大)堆,并将元素一次插入堆中
  • 当堆的容量超过K,就删除堆顶
  • 插入结束后,堆顶就是第K个最大元素

image.png

js实现最小堆

步骤

  • 在类里,申明一个数组,用来装元素
  • 主要方法:插入、删除堆顶,获取堆顶,获取堆大小

    1. class minHeap {
    2. constructor() {
    3. this.heap = []
    4. }
    5. }

    插入

  • 将值插入堆的底部,即数组的尾部

  • 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值
  • 大小为k的堆中插入元素的时间复杂度为O(logk) ```javascript class MinHeap { constructor() {
    1. this.heap = []
    }
    1. // 交换两个元素
    swap(n1, n2) {
    1. const temp = this.heap[n1]
    2. this.heap[n1] = this.heap[n2]
    3. this.heap[n2] = temp
    }
    1. // 获取父节点的值
    getParentIndex(index) {
    1. // 除以2取商
    2. // return Math.floor((index - 1) / 2)
    3. return (index - 1) >> 1
    }
    1. // 上移逻辑
    shiftUp(index) {
    1. // 当遇到堆顶则停止上移
    2. if(index === 0) return
    3. const parentIndex = this.getParentIndex(index)
    4. // 利用父节点的值永远小于子节点这个特性(最小堆)
    5. if(this.heap[parentIndex] > this.heap[index]) {
    6. // 交换
    7. this.swap(parentIndex, index)
    8. // 上移
    9. this.shiftUp(parentIndex)
    10. }
    }
    1. // 插入
    insert(value) {
    1. // 插入到堆底
    2. this.heap.push(value)
    3. this.shiftUp(this.heap.length -1)
    } }

const heap = new MinHeap() heap.insert(3) heap.insert(2) heap.insert(1) heap.insert(5) heap.insert(8)

  1. <a name="wQAAg"></a>
  2. ### 删除堆顶
  3. - 用数组尾部的元素替换堆顶并删除数组尾部的元素(直接删除堆顶会破坏堆结构)
  4. - 然后下移:将新的堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶
  5. - 大小为K的堆中删除堆顶的时间复杂度为O(logk)
  6. ```javascript
  7. class MinHeap {
  8. constructor() {
  9. this.heap = []
  10. }
  11. swap(n1, n2) {
  12. const temp = this.heap[n1]
  13. this.heap[n1] = this.heap[n2]
  14. this.heap[n2] = temp
  15. }
  16. getParentIndex(index) {
  17. // 除以2取商
  18. // return Math.floor((index - 1) / 2)
  19. return (index - 1) >> 1
  20. }
  21. // 获取左节点
  22. getLeftIndex(index) {
  23. return (index * 2) + 1
  24. }
  25. // 获取右节点
  26. getRightIndex(index) {
  27. return (index * 2) + 2
  28. }
  29. shiftUp(index) {
  30. if(index === 0) return
  31. const parentIndex = this.getParentIndex(index)
  32. if(this.heap[parentIndex] > this.heap[index]) {
  33. this.swap(parentIndex, index)
  34. this.shiftUp(parentIndex)
  35. }
  36. }
  37. // 下移逻辑
  38. shiftDown(index) {
  39. const leftIndex = this.getLeftIndex(index)
  40. const rightIndex = this.getRightIndex(index)
  41. // 利用子节点的值永远大于父节点(最小堆)
  42. if(this.heap[leftIndex] < this.heap[index]) {
  43. this.swap(leftIndex, index)
  44. this.shiftDown(leftIndex)
  45. }
  46. if(this.heap[rightIndex] < this.heap[index]) {
  47. this.swap(rightIndex, index)
  48. this.shiftDown(rightIndex)
  49. }
  50. }
  51. insert(value) {
  52. this.heap.push(value)
  53. this.shiftUp(this.heap.length -1)
  54. }
  55. // 删除堆顶
  56. pop(){
  57. this.heap[0] = this.heap.pop()
  58. this.shiftDown(0)
  59. }
  60. }
  61. const heap = new MinHeap()
  62. heap.insert(3)
  63. heap.insert(2)
  64. heap.insert(1)
  65. heap.insert(5)
  66. heap.insert(8)
  67. heap.pop()

获取堆顶和堆的大小

  • 获取堆顶:返回数组头部
  • 获取堆的大小:返回数组的长度

    题目

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

    在未排序的数组中找到第 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

说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

思路:

  • 看到第K个最大元素
  • 考虑使用最小堆来解决

解题步骤:

  • 构建一个最小堆,把数组中的元素插入堆中
  • 当堆的容量超过K,就删除堆顶
  • 插入结束后,堆顶就是第K个最大元素 ```javascript class MinHeap { constructor() {

    1. this.heap = []

    } swap(n1, n2) {

    1. const temp = this.heap[n1]
    2. this.heap[n1] = this.heap[n2]
    3. this.heap[n2] = temp

    } getParentIndex(index) {

    1. // 除以2取商
    2. // return Math.floor((index - 1) / 2)
    3. return (index - 1) >> 1

    } getLeftIndex(index) {

    1. // 除以2取商
    2. // return Math.floor((index - 1) / 2)
    3. return (index * 2) + 1

    } getRightIndex(index) {

    1. // 除以2取商
    2. // return Math.floor((index - 1) / 2)
    3. return (index * 2) + 2

    } shiftUp(index) {

    1. if(index === 0) return
    2. const parentIndex = this.getParentIndex(index)
    3. if(this.heap[parentIndex] > this.heap[index]) {
    4. this.swap(parentIndex, index)
    5. this.shiftUp(parentIndex)
    6. }

    } shiftDown(index) {

    1. const leftIndex = this.getLeftIndex(index)
    2. const rightIndex = this.getRightIndex(index)
    3. if(this.heap[leftIndex] < this.heap[index]) {
    4. this.swap(leftIndex, index)
    5. this.shiftDown(leftIndex)
    6. }
    7. if(this.heap[rightIndex] < this.heap[index]) {
    8. this.swap(rightIndex, index)
    9. this.shiftDown(rightIndex)
    10. }

    } insert(value) {

    1. this.heap.push(value)
    2. this.shiftUp(this.heap.length -1)

    } pop(){

    1. this.heap[0] = this.heap.pop()
    2. this.shiftDown(0)

    } peek() {

    1. return this.heap[0]

    } size() {

    1. return this.heap.length

    } }

/**

  • @param {number[]} nums
  • @param {number} k
  • @return {number} */ var findKthLargest = function(nums, k) { // 构建一个最小堆 const minHeap = new MinHeap() nums.forEach(item => {
    1. // 将数组中的元素插入到堆中
    2. minHeap.insert(item)
    3. // 当堆的大小超过K
    4. if(minHeap.size() > k) {
    5. // 就删除堆顶元素
    6. minHeap.pop()
    7. }
    }) // 返回堆顶,因为长度为k,堆顶是堆中最小值,所以堆顶就是第K大的元素 return minHeap.peek()

};

  1. <a name="oWX1O"></a>
  2. ## 2.前K个高频要素 - 347
  3. 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
  4. 示例 1:<br />输入: nums = [1,1,1,2,2,3], k = 2<br />输出: [1,2]
  5. 示例 2:<br />输入: nums = [1], k = 1<br />输出: [1]
  6. 提示:
  7. - 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
  8. - 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
  9. - 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
  10. - 你可以按任意顺序返回答案。
  11. 思路1(暴力统计法-时间复杂度不符合):
  12. - 统计nums中数字出现的频率
  13. - 降序排列
  14. - 取出前k个元素
  15. 解题步骤1:
  16. - 利用map统计频率
  17. - 对map按照频率降序排列
  18. - 返回前k个元素
  19. ```javascript
  20. /**
  21. * 暴力统计法(时间复杂度不符合)
  22. * @param {number[]} nums
  23. * @param {number} k
  24. * @return {number[]}
  25. */
  26. var topKFrequent = function(nums, k) {
  27. // 利用map统计元素出现的频率
  28. const map = new Map()
  29. nums.forEach(n => {
  30. map.set(n, map.has(n) ? map.get(n) + 1 : 1)
  31. })
  32. // 对map按照频率进行排序
  33. // Array.sort()的时间复杂度最优为nlogn(快排)
  34. const list = Array.from(map).sort((a, b) => b[1] - a[1])
  35. // 返回数组中前k个元素
  36. return list.slice(0, k).map(n => n[0])
  37. };

思路2(堆):

  • 建立一个最小堆
  • 把元素以及频率插入到堆中,并按照频率进行排序
  • 维持堆的大小永远为k
  • 堆中前k个元素即为频率前k高的元素 ```javascript class MinHeap { constructor() {

    1. this.heap = []

    } swap(n1, n2) {

    1. const temp = this.heap[n1]
    2. this.heap[n1] = this.heap[n2]
    3. this.heap[n2] = temp

    } getParentIndex(index) {

    1. // 除以2取商
    2. // return Math.floor((index - 1) / 2)
    3. return (index - 1) >> 1

    } getLeftIndex(index) {

    1. // 除以2取商
    2. // return Math.floor((index - 1) / 2)
    3. return (index * 2) + 1

    } getRightIndex(index) {

    1. // 除以2取商
    2. // return Math.floor((index - 1) / 2)
    3. return (index * 2) + 2

    } shiftUp(index) {

    1. if(index === 0) return
    2. const parentIndex = this.getParentIndex(index)
    3. if(this.heap[parentIndex] && this.heap[parentIndex].value > this.heap[index].value) {
    4. this.swap(parentIndex, index)
    5. this.shiftUp(parentIndex)
    6. }

    } shiftDown(index) {

    1. const leftIndex = this.getLeftIndex(index)
    2. const rightIndex = this.getRightIndex(index)
    3. // 比较的是value,而且计算出来的index很有可能不存在,需要做改动
    4. if(this.heap[leftIndex] && this.heap[leftIndex].value < this.heap[index].value) {
    5. this.swap(leftIndex, index)
    6. this.shiftDown(leftIndex)
    7. }
    8. if(this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value) {
    9. this.swap(rightIndex, index)
    10. this.shiftDown(rightIndex)
    11. }

    } insert(value) {

    1. this.heap.push(value)
    2. this.shiftUp(this.heap.length -1)

    } pop(){

    1. this.heap[0] = this.heap.pop()
    2. this.shiftDown(0)

    } pick() {

    1. return this.heap[0]

    } size() {

    1. return this.heap.length

    } }

/**

  • @param {number[]} nums
  • @param {number} k
  • @return {number[]} */ var topKFrequent = function(nums, k) { // 利用map统计元素出现的频率 const map = new Map() nums.forEach(n => {
    1. map.set(n, map.has(n) ? map.get(n) + 1 : 1)
    }) const h = new MinHeap() // 注意字典的forEach循环,value在前,key在后 map.forEach((value, key) => {
    1. // 按照value(频率)建立堆
    2. h.insert({value, key})
    3. // 保证堆的大小在k
    4. if(h.size() > k) {
    5. h.pop()
    6. }
    })
    1. // 返回堆的key(元素)数组
    return h.heap.map(item => item.key)

};

  1. <a name="lhz2d"></a>
  2. ## 3.合并K个升序链表 - 23
  3. 给你一个链表数组,每个链表都已经按升序排列。
  4. 请你将所有链表合并到一个升序链表中,返回合并后的链表。
  5. 示例 1:<br />输入:lists = [[1,4,5],[1,3,4],[2,6]]<br />输出:[1,1,2,3,4,4,5,6]<br />解释:链表数组如下:<br />[<br /> 1->4->5,<br /> 1->3->4,<br /> 2->6<br />]<br />将它们合并到一个有序链表中得到。<br />1->1->2->3->4->4->5->6
  6. 示例 2:<br />输入:lists = []<br />输出:[]
  7. 示例 3:<br />输入:lists = [[]]<br />输出:[]
  8. 提示:
  9. - k == lists.length
  10. - 0 <= k <= 10^4
  11. - 0 <= lists[i].length <= 500
  12. - -10^4 <= lists[i][j] <= 10^4
  13. - lists[i] 按 升序 排列
  14. - lists[i].length 的总和不超过 10^4
  15. 思路:
  16. - 新链表的下一个节点一定是k个链表头中最小的节点
  17. - 考虑使用最小堆
  18. 解题步骤:
  19. - 构建一个最小堆,并依次把链表头插入堆中
  20. - 弹出堆顶接到输出链表,并将堆顶所在链表的新链表头插入到堆中
  21. - 不断重复2
  22. - 等堆元素全部弹出,合并工作就完成了
  23. ```javascript
  24. class MinHeap {
  25. constructor() {
  26. this.heap = []
  27. }
  28. swap(n1, n2) {
  29. const temp = this.heap[n1]
  30. this.heap[n1] = this.heap[n2]
  31. this.heap[n2] = temp
  32. }
  33. getParentIndex(index) {
  34. // 除以2取商
  35. // return Math.floor((index - 1) / 2)
  36. return (index - 1) >> 1
  37. }
  38. getLeftIndex(index) {
  39. // 除以2取商
  40. // return Math.floor((index - 1) / 2)
  41. return (index * 2) + 1
  42. }
  43. getRightIndex(index) {
  44. // 除以2取商
  45. // return Math.floor((index - 1) / 2)
  46. return (index * 2) + 2
  47. }
  48. shiftUp(index) {
  49. if(index === 0) return
  50. const parentIndex = this.getParentIndex(index)
  51. if(this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val) {
  52. this.swap(parentIndex, index)
  53. this.shiftUp(parentIndex)
  54. }
  55. }
  56. shiftDown(index) {
  57. const leftIndex = this.getLeftIndex(index)
  58. const rightIndex = this.getRightIndex(index)
  59. if(this.heap[leftIndex] && this.heap[leftIndex].val < this.heap[index].val) {
  60. this.swap(leftIndex, index)
  61. this.shiftDown(leftIndex)
  62. }
  63. if(this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[index].val) {
  64. this.swap(rightIndex, index)
  65. this.shiftDown(rightIndex)
  66. }
  67. }
  68. insert(value) {
  69. this.heap.push(value)
  70. this.shiftUp(this.heap.length -1)
  71. }
  72. // 改造pop 返回堆顶
  73. pop(){
  74. // 当只有一个元素,直接弹出返回
  75. if(this.size() === 1) return this.heap.shift()
  76. const top = this.heap[0]
  77. this.heap[0] = this.heap.pop()
  78. this.shiftDown(0)
  79. return top
  80. }
  81. pick() {
  82. return this.heap[0]
  83. }
  84. size() {
  85. return this.heap.length
  86. }
  87. }
  88. /**
  89. * Definition for singly-linked list.
  90. * function ListNode(val, next) {
  91. * this.val = (val===undefined ? 0 : val)
  92. * this.next = (next===undefined ? null : next)
  93. * }
  94. */
  95. /**
  96. * @param {ListNode[]} lists
  97. * @return {ListNode}
  98. */
  99. var mergeKLists = function(lists) {
  100. // 定义一个返回链表
  101. const res = new ListNode(0)
  102. // 定义最小堆
  103. const h = new MinHeap()
  104. // 指针
  105. let p = res
  106. // 将链表头全部按照大小插入堆中
  107. // 时间复杂度:O(k) k 为数组大小
  108. lists.forEach(l => {
  109. // 会自动根据链表头val的大小排序链表,堆顶就是最小的链表
  110. if(l) h.insert(l)
  111. })
  112. // 堆中有值
  113. // 时间复杂度:O(n) n为链表大小
  114. while(h.size()) {
  115. // 拿到堆顶的最小链表
  116. const n = h.pop()
  117. // 将最小链表插入到返回链表中
  118. p.next = n
  119. // 移动指针到下一个链表
  120. p = p.next
  121. // 将新链表的头插入到堆中去自动比较所有链表头的大小
  122. // 时间复杂度:logk, 永远都是k个链表的头部在比较
  123. if(p.next) h.insert(n.next)
  124. }
  125. // 返回链表的next
  126. return res.next
  127. // 空间复杂度:O(k) 堆的大小
  128. };