[路飞]_leetcode-347-前K个高频元素

492 阅读1分钟

「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

[题目地址] [B站地址]

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

示例 1:

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

示例 2:

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

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶: 你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n **是数组大小。

本题需要找到前 k 个高频元素,通常维护前 k 个最值的问题,都可以通过堆来解决

堆的性质

堆.png

堆的插入

插入堆就是在数组的末尾插入元素

然后判断新插入元素是否影响了当前三元组的平衡。以大顶堆为例,如果插入元素的值大于根节点的值,则交换两个节点,直到新插入元素所在三元组满足根节点的值为当前三元组最值为止

堆的弹出

堆的弹出操作指弹出堆顶元素,对应数组下标 0 的元素,此时更新数组末尾元素为数组第一个元素,并删除之前末尾元素

然后判断新的堆顶元素是否影响了当前三元组的平衡。以大顶堆为例,如果新的堆顶元素不是当前三元组最大值,则与当前三元组最大值进行交换,知道它成为新的三元组的最大值为止

题解1

理解了堆的性质,我们来看一下本题的第一种题解

  1. 使用 map 维护数组中元素的值以及出现的次数
  2. 创建一个小顶堆用来维护前 k 个高频元素
  3. 遍历 map,如果堆中的元素大于等于 k个,并且堆顶元素大于本次循环数据的 val 值,不进行插入,否则本次循环数据插入堆中
  4. 当堆不为空的时候,将堆顶元素弹出,把弹出值的 key 放到结果数组中,最后返回结果数组即可

代码如下:

class MinHeap {
    constructor(max){
        this.arr = [];
        this.size = 0;
        this.max = max;
    }
    // 插入元素
    push(val){
        this.arr.push(val);
        this.size++;
        if(this.size>1){
            let cur = this.size-1,
            parentInd = (cur-1)>>1;
            while(cur>0 && this.arr[parentInd].val>this.arr[cur].val){
                [this.arr[parentInd],this.arr[cur]] = 
                [this.arr[cur],this.arr[parentInd]]
                cur = parentInd;
                parentInd = (cur-1)>>1;
            }
        }
        while(this.size>this.max) this.pop();
    }
    // 弹出堆顶元素
    pop(){
        if(this.empty()) return false;
        const res = this.arr[0]
        this.arr[0] = this.arr.pop();
        this.size--;
        let cur = 0,
        childl = 1,
        childr = 2;
        while(
            (childl<this.size&&this.arr[childl].val<this.arr[cur].val) ||
            (childr<this.size&&this.arr[childr].val<this.arr[cur].val)
        ){
            if(childr<this.size&&this.arr[childr].val<this.arr[childl].val){
                [this.arr[cur],this.arr[childr]] = 
                [this.arr[childr],this.arr[cur]]
                cur = childr;
            }else{
                [this.arr[cur],this.arr[childl]] = 
                [this.arr[childl],this.arr[cur]]
                cur = childl;
            }
            childl =cur*2+1,childr = cur*2+2;
        }
        return res;
    }
    // 判断堆是否为空
    empty(){
        return this.size === 0
    }
    // 返回堆顶元素
    top(){
        return this.arr[0]
    }
}

var topKFrequent = function(nums, k) {
    // map 维护数组中的值以及出现的次数
    const map = new Map();
    for(let i = 0;i<nums.length;i++){
        const cur = nums[i]
        if(map.has(cur)){
            map.set(cur,map.get(cur)+1)
        }else{
            map.set(cur,1)
        }
    }
    // 创建小顶堆维护前k个高频元素
    const heap = new MinHeap(k);
    map.forEach((val,key) => {
        if(heap.size<k || heap.top().val<val){
            heap.push({val,key})
        }
    })
    // 将堆中元素放入结果数组
    const ret = [];
    while(!heap.empty()){
        ret.push(heap.pop().key)
    }
    return ret;
};

题解2

  1. 使用 map 维护数组中元素的值以及出现的次数
  2. 通过 Array.frommap 转为数组
  3. 根据元素出现的次数进行降序排序
  4. 将前k个高频元素的值放到结果数组

代码如下:

var topKFrequent = function(nums, k) {
  // map 维护数组中的值以及出现的次数
  const map = new Map();
  for(let i = 0;i<nums.length;i++){
      const cur = nums[i]
      if(map.has(cur)){
          map.set(cur,map.get(cur)+1)
      }else{
          map.set(cur,1)
      }
  }
  // 将map转为数组
  const arr = Array.from(map);
  // 根据元素出现的次数进行降序排序
  arr.sort((a,b) => b[1]-a[1])
  // 将前k个高频元素的值放到结果数组
  const ret = [];
  for(let i = 0;i<k;i++){
      ret.push(arr[i][0])
  }
  return ret;
};

至此,我们就通过两种方式完成了 leetcode-347-前K个高频元素

如有任何问题或建议,欢迎留言讨论!