数据结构与算法之堆: Leetcode 347. 前K个高频元素 (Typescript版)

前K个高频元素

  • https://leetcode.cn/problems/top-k-frequent-elements/

描述

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

示例 1

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

示例 2

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

提示

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

进阶

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

算法实现

1 )使用堆排序

class MinHeap {
    heap: Record<string, any>[] = [];
    // 交换节点位置
    swap(i1, i2) {
        [this.heap[i1], this.heap[i2]] = [this.heap[i2], this.heap[i1]];
    }
    // 获得父节点
    getParentIndex(i) {
        return (i - 1) >> 1;
    }
    // 获取左子节点
    getLeftIndex(i) {
        return (i << 1) + 1; // 极客写法
    }
    // 获取右子节点
    getRightIndex(i) {
        return (i << 1) + 2;
    }
    // 向上移动
    shiftUp(index) {
        // 如果到了堆顶元素,index是0,则不要再上移了
        if(!index) {
            return;
        }
        let parentIndex = this.getParentIndex(index)
        if(this.heap[parentIndex]?.value > this.heap[index].value) {
            this.swap(parentIndex, index)
            this.shiftUp(parentIndex)
        }
    }
    // 下移
    shiftDown(index) {
        // 边界1:如果到了堆尾元素,则不要再下移了
        if(index >= this.heap.length - 1) {
            return;
        }
        const size = this.size();
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if (leftIndex < size && this.heap[leftIndex]?.value < this.heap[index].value) {
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        // 注意,这里的 this.heap[index].value 和上面重复,不能使用变量,因为上个if中存在 swap 和 shiftDown, 使用变量有可能出问题
        if (rightIndex < size && this.heap[rightIndex]?.value < this.heap[index].value) {
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    // 插入
    insert(value) {
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }
    // 删除堆顶
    pop() {
        // pop()方法删除数组最后一个元素并返回,赋值给堆顶
        this.heap[0] = this.heap.pop();
        // 对堆顶重新排序
        this.shiftDown(0);
    }
    // 获取堆顶
    peak() {
        return this.heap[0];
    }
    // 获取堆的大小
    size() {
        return this.heap.length;
    }
}

function topKFrequent(nums: number[], k: number): number[] {
    let map = new Map();
    nums.forEach(n => {
        map.set(n, map.has(n)? map.get(n) + 1 : 1);
    })
    const h = new MinHeap();
    map.forEach((value, key) => {
        h.insert({value, key});
        // 保证堆的尺寸是k
        (h.size() > k) &&  h.pop();
    })
    return h.heap.map(a => a.key);
}
  • 这里改造了之前的 MinHeap 最小堆类
  • 原来是值比较,现在是对象的比较

2 )使用字典

function topKFrequent(nums: number[], k: number): number[] {
    let map = new Map();
    nums.forEach(n => {
        map.set(n, map.has(n)? map.get(n) + 1 : 1);
    })
    // console.log(map); // 这是一个map结构
    // console.log(Array.from(map)); // 将map转换为二维数组,[[值, 频率], [值, 频率]] 这是无序的

    // 对Array.from(map)产生的二维矩阵进行频率的排序
    const list = Array.from(map).sort((a,b) => b[1] - a[1])
    return list.slice(0, k).map(i => i[0]); // 返回按频率排序的前K个元素值
}
  • 统计每个元素出现的次数(频率)
  • 通过Map结构来处理
  • 分析时间复杂度:O(nlogn)
  • 一个forEach循环O(n)
  • 原生sort排序,O(nlogn)
  • 总体来说O(nlogn)
  • 如果题目要求,返回的前K个高频元素不考虑顺序,我们一下就要想到用堆排序

你可能感兴趣的:(Data,Structure,and,Algorithms,leetcode,算法)