[路飞]前 K 个高频元素

236 阅读2分钟

记录 1 道算法题

前 K 个高频元素

leetcode-cn.com/problems/to…


前 K 个涉及道排序的问题,题目没有要求结果一定是升序或降序,所以可以用堆或快排进行解决。

首先遍历一遍数组用一个对象记录每个元素出现的次数。然后将这个对象转换成二维数组进行存储排序。如果是堆的话,直接把堆的结果输出就行。如果是快排则需要截取数组的前 k 个。

堆采用小顶堆,即堆顶是堆内最小元素,堆底是堆内最大元素

堆的实现就不赘诉了,可以看这篇文章

    function topKFrequent(nums, k) {
        const map = {}
        // 收集每个元素出现的次数
        for (let i = 0; i < nums.length; i++) {
          let item = nums[i]
          if (map[item]) {
            map[item]++
          } else {
            map[item] = 1
          }
        }

        const heap = new Heap((a, b) => a[1] - b[1])
        nums = Object.entries(map)
        for (let i = 0; i < nums.length; i++) {
          const item = nums[i]
          // 排除掉比堆顶还要小的元素
          if (heap.size() > k && item[1] < heap.data[0][1]) return
          
          // 存入堆中
          heap.push(item)
          if (heap.size() > k) {
            heap.pop()
          }
        }
        
        // 因为结果只要元素,所以要对二维数组进行处理
        return heap.data.reduce((a, b) => {
          a.push(+b[0])
          return a
        }, [])
    }
  1. 快排

提供记录元素出现次数的数组和某一段的开始下标和结束下标,当比较的基准点的下标是小于 k 的时候,说明我们想要的前 k 个元素在左边。于是递归处理左边,直到基准点 等于 k。这时候数组 0-k 就是我们需要的前 K 个高频元素,而且是按出现次数降序的。

快排的实现可以看这篇文章

    function topKFrequent(nums, k) {
        const map = {}
        for (let i = 0; i < nums.length; i++) {
          let item = nums[i]
          if (map[item]) {
            map[item]++
          } else {
            map[item] = 1
          }
        }

        nums = Object.entries(map)
        // 开始排序
        fastsort(nums, 0, nums.length - 1, k)
        
        // 快排是对整个数组原地排序,所以要截取前 k 个
        return nums.slice(0, k).reduce((a, b) => {
          a.push(+b[0])
          return a
        }, [])
   }

  function fastsort(nums, start, end, k) {
    默认用每一段的第一个元素作为基准点
    let pivot = nums[start]
    let left = start + 1
    // 然后从第二个开始进行归类。
    // 因为想要降序,所以分为比基准点大的放在左边,比基准点小的放在右边
    for (let i = left; i <= end; i++) {
      let item = nums[i]
      if (item[1] > pivot[1]) {
        swap(nums, i, left)
        left++
      }
    }

    // 分类完之后, left 是右边的开始下标,所以 left - 1 就是左边的结束下标,
    // 和基准点进行交换,这样 left - 1 就是基准点下标
    swap(nums, start, left - 1)
    // 如果基准点下标是 k - 1 则说明前 k 个以及确定了。
    // 如果不是则说明还没排序完,因为可能是 [10] 5 [2,3,4] 或者 [4, 6, 7] 3 [1, 2]
    // 这样如果是想要前 3 个 或 前 2 个的时候,都是不准确的。
    if (left > k) {
      fastsort(nums, start, left - 1, k)
    } else if (left === k) {
      return
    } else {
      fastsort(nums, left, end, k)
    }
  }

  function swap(arr, a, b) {
    ;[arr[a], arr[b]] = [arr[b], arr[a]]
  }

结束