[路飞]_前 K 个高频元素

136 阅读2分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战

leetcode-347 前 K 个高频元素

题目介绍

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例1

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

示例2

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

提示:

  • 1 <= nums.length <= 10^5
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的 进阶: 你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n **是数组大小。

解题思路

题目要求返回出现频率前 k 高的元素,因此需要先遍历一次整个数组中的元素,记录每个元素出现的次数,为了提高效率,可以使用 Map 来存储每个元素出现的次数

这是第一步,这一步是不可避免的,每一种解法都需要先进行这一步,接下去就是排序的方法,下面讲两种解法

思路一:直接排序

直接排序是在统计完每个元素出现的次数之后直接利用 sort 方法进行频率从高到低的排序,然后截取排序之后数组的前 k 位

解题步骤

  1. 遍历整个数组,将每个元素的次数记录在 Map 结构中
  2. map.keys() 按出现的次数从高到低进行排序
  3. 返回排序后数组的前 k

解题代码

var topKFrequent = function(nums, k) {
    const map = new Map()
    nums.map(v => {
        if(!map.has(v)) {
            map.set(v, 1)
        } else {
            map.set(v, map.get(v) + 1)
        }
    })
    const arr = [...map.keys()]
    arr.sort((a, b) => map.get(b) - map.get(a))
    return arr.slice(0, k)
};

思路二:小顶堆

对于这种返回前 k 大类型的题目,都可以使用堆来做,维护一个大小为 k 的小顶堆,然后将每个元素及其出现的次数插入到小顶堆中,最后返回小顶堆中的元素就可以了

因为这道题没有规定我们返回的顺序,所以最后我们可以直接返回堆中的所有元素,不需要进行排序

解题步骤

  1. 遍历整个数组,将每个元素的次数记录在 Map 结构中
  2. 维护一个大小为 k 的小顶堆 minHeap
  3. 依次将 map.entries() 中的每个键值对插入到小顶堆中
  4. 返回小顶堆中的所有元素的键组成的数组

解题代码

var topKFrequent = function(nums, k) {
    const map = new Map()
    nums.map(v => {
        if (!map.has(v)) {
            map.set(v, 1)
        } else {
            map.set(v, map.get(v) + 1)
        }
    })

    const minHeap = new Heap(k)
    // 将所有的键值对插入到小顶堆中
    for(let item of map.entries()) {
        minHeap.push(item)
    }
    // 小顶堆中保存的元素即为频率前 k 高的元素
    return minHeap.toArray()
};

class Heap {
    constructor(k) {
        this.k = k
        this.arr = []
    }
    
    // 直接返回小顶堆中所有元素的键
    toArray() {
        return this.arr.map(v => {return v[0]})
    }

    // 向小顶堆中插入元素
    push(val) {
        if (this.arr.length < this.k) {
            this.arr.push(val)
            this._sortBack()
        } else if (val[1] > this.arr[0][1]) {
            this.arr[0] = val
            this._sortFront()
        }
    }

    // 从底部向上调整堆结构
    _sortBack() {
        let i = this.arr.length - 1
        while(i > 0 && this.arr[Math.floor((i - 1) / 2)][1] > this.arr[i][1]) {
            [this.arr[Math.floor((i - 1) / 2)], this.arr[i]] = [this.arr[i], this.arr[Math.floor((i - 1) / 2)],]
            i = Math.floor((i - 1) / 2)
        }
    }

    // 从顶部向下调整堆结构
    _sortFront() {
        let i = 0
        while (i * 2 + 1 < this.arr.length) {
            let temp = i
            if (this.arr[temp][1] > this.arr[i * 2 + 1][1]) temp = i * 2 + 1
            if (i * 2 + 2 < this.arr.length && this.arr[temp][1] > this.arr[i * 2 + 2][1]) temp = i * 2 + 2
            if (temp === i) break
            [this.arr[i], this.arr[temp]] = [this.arr[temp], this.arr[i]]
            i = temp
        }
    }
}