• 数据结构与算法之堆: 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]
    
    • 1
    • 2

    示例 2

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

    提示

    • 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(i, j) {
            [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
        }
        // 获得父节点
        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;
            const 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[] {
        const 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 这里改造了之前的 MinHeap 最小堆类
    • 原来是值比较,现在是对象的比较

    2 )使用字典

    function topKFrequent(nums: number[], k: number): number[] {
        const 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个元素值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 统计每个元素出现的次数(频率)
    • 通过Map结构来处理
    • 分析时间复杂度:O(nlogn)
    • 一个forEach循环O(n)
    • 原生sort排序,O(nlogn)
    • 总体来说O(nlogn)
    • 如果题目要求,返回的前K个高频元素不考虑顺序,我们一下就要想到用堆排序
  • 相关阅读:
    如何封装Vue组件并上传到npm
    触觉智能分享-SSD20X实现升级显示进度条
    Altium 22 去掉封装内部规则检查
    Goby 漏洞发布|深信服下一代防火墙 loadfile.php 文件读取漏洞
    线上展厅方案如何快速选择 广州商迪
    计算二进制中1的个数
    计算机网络——运输层作业5.2
    数学建模(四):分类
    Kafka生产者和消费者基本操作
    网络安全(黑客)小白自学
  • 原文地址:https://blog.csdn.net/Tyro_java/article/details/133493400