xxleyi / loop_invariants

记录以及整理「循环不变式」视角下的算法题解

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

leetcode 347. 前 K 个高频元素

xxleyi opened this issue · comments

题:

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

 
示例 1:

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

输入: nums = [1], k = 1
输出: [1]
 

提示:

你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/top-k-frequent-elements
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解:

本质还是排序,综合各种情况的话,最佳方案是堆排序。

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */


// counter and sort
var topKFrequent = function(nums, k) {
  const counter = new Map()
  for (let e of nums) {
    if (counter.has(e)) {
      counter.set(e, counter.get(e) + 1)
    } else {
      counter.set(e, 1)
    }
  }
  return ([...new Set(nums)]).sort((a, b) => counter.get(b) - counter.get(a)).slice(0, k)
};

// counter and top K heap
var topKFrequent = (nums, k) => {
  const counter = new Map(), heap = Array(k + 1)
  for (let num of nums) {
    if(counter.has(num)) counter.set(num, counter.get(num) + 1)
    else counter.set(num, 1)
  }
    
  // 如果元素数量小于等于 k
  if(counter.size <= k) {
    return [...counter.keys()]
  }

  // 原地建堆,从后往前,自上而下式建小顶堆
  const buildHeap = () => {
    // 从最后一个非叶子节点开始堆化
    for (let i = k / 2 | 0; i > 0; i--) {
      heapify(i)
    }
  }

  // 堆化
  const heapify = (i) => {
    // 自上而下式堆化
    while (true) {
      let top = i
      const left = 2 * i, right = 2 * i + 1

      if (shouldUpdateTop(left, top)) {
        top = left
      }
      if (shouldUpdateTop(right, top)) {
        top = right
      }

      if (top !== i) {
        swap(i, top)
        i = top
      } else {
        break
      }
    }
  }

  // 判断是否需要更新最小索引
  const shouldUpdateTop = (i, top) => i <= k && counter.get(heap[i]) < counter.get(heap[top])

  // 交换
  const swap = (i , j) => {
    [heap[i], heap[j]] = [heap[j], heap[i]]
  }
    

  let i = 0
  const iterator = counter.entries()

  // 先填满堆
  while (i++ < k) {
    heap[i] = iterator.next().value[0]
  }

  // 然后初始化堆
  buildHeap()

  // 再遍历所有剩余元素,更新堆
  for (const [key, value] of iterator) {
    // 使用 index 1 作为堆顶,计算较为方便
    if (value > counter.get(heap[1])) {
      // 重新堆化
      heap[1] = key
      heapify(1)
    }
  }

  // 返回堆中元素
  return heap.slice(1)
};


// counter and bucket sort
var topKFrequent = function (nums, k) {
  let counter = new Map()
  for (let num of nums) {
    if (counter.has(num)) counter.set(num, counter.get(num) + 1)
    else counter.set(num, 1)
  }

  // 桶排序
  const bucketSort = () => {
    let arr = Array(nums.length + 1)
    for (const [key, value] of counter.entries()) {
      // 利用映射关系(出现频率作为下标)将数据分配到各个桶中
      if (!arr[value]) {
        arr[value] = [key]
      } else {
        arr[value].push(key)
      }
    }
    return arr
  }

  const res = [], arr = bucketSort()
  // 倒序遍历获取出现频率最大的前k个数
  for (let i = arr.length - 1; i > 0 && res.length < k; i--) {
    if (arr[i]) {
      res.push(...arr[i])
    }
  }
  return res
};