使用哈希表和最小堆找到数组中前 K 个高频元素 - JavaScript 实现

2025/3/15
本文详细介绍了如何使用哈希表和最小堆(Min-Heap)结合的方法来找到数组中前 K 个高频元素。通过遍历数组并使用哈希表统计元素频率,然后使用最小堆维护前 K 个高频元素,最终实现了一个高效的算法。文章还提供了 JavaScript 的代码实现和详细解释。
一个程序员在电脑前编写代码,屏幕上显示哈希表和最小堆的示意图

要找到数组中前 K 个高频元素,可以使用哈希表和最小堆(Min-Heap)结合的方法。以下是详细的步骤和代码实现:

1. 使用哈希表统计元素频率

首先,我们遍历数组,使用哈希表(JavaScript 中的 Map)来统计每个元素出现的频率。

2. 使用最小堆维护前 K 个高频元素

接下来,我们使用最小堆来维护前 K 个高频元素。最小堆的特点是堆顶元素是堆中最小的元素,这样我们可以很容易地替换掉堆顶元素,从而保持堆中始终是前 K 个高频元素。

3. 实现代码

以下是使用 JavaScript 实现的代码:

class MinHeap {
    constructor() {
        this.heap = [];
    }

    push(val) {
        this.heap.push(val);
        this.bubbleUp(this.heap.length - 1);
    }

    pop() {
        const min = this.heap[0];
        const end = this.heap.pop();
        if (this.heap.length > 0) {
            this.heap[0] = end;
            this.bubbleDown(0);
        }
        return min;
    }

    bubbleUp(index) {
        const element = this.heap[index];
        while (index > 0) {
            const parentIndex = Math.floor((index - 1) / 2);
            const parent = this.heap[parentIndex];
            if (element[1] >= parent[1]) break;
            this.heap[index] = parent;
            index = parentIndex;
        }
        this.heap[index] = element;
    }

    bubbleDown(index) {
        const length = this.heap.length;
        const element = this.heap[index];
        while (true) {
            let leftChildIndex = 2 * index + 1;
            let rightChildIndex = 2 * index + 2;
            let swap = null;
            let leftChild, rightChild;

            if (leftChildIndex < length) {
                leftChild = this.heap[leftChildIndex];
                if (leftChild[1] < element[1]) {
                    swap = leftChildIndex;
                }
            }

            if (rightChildIndex < length) {
                rightChild = this.heap[rightChildIndex];
                if (
                    (swap === null && rightChild[1] < element[1]) ||
                    (swap !== null && rightChild[1] < leftChild[1])
                ) {
                    swap = rightChildIndex;
                }
            }

            if (swap === null) break;
            this.heap[index] = this.heap[swap];
            index = swap;
        }
        this.heap[index] = element;
    }

    size() {
        return this.heap.length;
    }

    peek() {
        return this.heap[0];
    }
}

function topKFrequent(nums, k) {
    const frequencyMap = new Map();
    for (const num of nums) {
        frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1);
    }

    const minHeap = new MinHeap();
    for (const [num, freq] of frequencyMap.entries()) {
        if (minHeap.size() < k) {
            minHeap.push([num, freq]);
        } else if (freq > minHeap.peek()[1]) {
            minHeap.pop();
            minHeap.push([num, freq]);
        }
    }

    const result = [];
    while (minHeap.size() > 0) {
        result.push(minHeap.pop()[0]);
    }

    return result.reverse();
}

// 示例用法
const nums = [1, 1, 1, 2, 2, 3];
const k = 2;
console.log(topKFrequent(nums, k)); // 输出: [1, 2]

4. 代码解释

  • MinHeap 类:实现了一个最小堆,用于维护前 K 个高频元素。
  • topKFrequent 函数
    • 首先使用 Map 统计每个元素的频率。
    • 然后使用最小堆来维护前 K 个高频元素。
    • 最后将堆中的元素取出并返回。

5. 时间复杂度

  • 统计频率的时间复杂度为 O(N),其中 N 是数组的长度。
  • 维护最小堆的时间复杂度为 O(N log K),因为每次插入和删除操作的时间复杂度为 O(log K)。
  • 总体时间复杂度为 O(N log K)。

6. 空间复杂度

  • 哈希表和最小堆的空间复杂度为 O(N)。

这种方法在大多数情况下都能高效地找到前 K 个高频元素。

上次更新:

相关文章

npx完全指南:前端开发必备工具详解 | 20年架构师深度解析

本文由20年前端架构师深入解析npx工具,涵盖其核心功能、优势、高级用法、最佳实践及与npm/yarn的区别比较,帮助开发者掌握这一现代前端开发利器。

·前端开发

<处理关联数据的最佳实践:Article 与 Tags 的关系 | 开发指南>

<本文详细介绍了在开发中处理关联数据(如 Article 和 Tags 的多对多关系)的最佳实践,包括拆分业务逻辑、使用事务保证数据一致性、合理设计关联表结构、批量操作、幂等性和乐观锁等关键要点,并提供了基于 mysql2 和 Sequelize 的代码示例。>

·后端开发

Astro 静态站点生成器:构建高性能网站的最佳选择

Astro 是一个专注于构建快速、轻量级网站的静态站点生成器,支持多种前端框架,采用岛屿架构减少 JavaScript 加载,提升性能。

·前端开发

MySQL外键约束详解:维护数据一致性与完整性

本文详细介绍了MySQL中的外键约束(Foreign Key Constraint),包括其基本概念、创建方法、作用、级联操作、限制、修改与删除方法、查看方式以及最佳实践。通过合理使用外键约束,可以有效管理数据库中的数据关系,确保数据的准确性和可靠性。

·后端开发

MySQL JSON数据类型支持与使用指南 | 详细解析与示例

本文详细解析了MySQL从5.7版本开始支持的JSON数据类型,包括版本支持、创建JSON字段、插入与查询JSON数据、修改JSON数据、生成JSON、索引优化、性能与应用场景、注意事项及示例全流程。

·后端开发

SQL JOIN、LEFT JOIN 和 RIGHT JOIN 的区别与应用场景详解

本文详细介绍了 SQL 中 JOIN、LEFT JOIN 和 RIGHT JOIN 的区别,包括它们的作用、语法、示例以及实际应用场景,帮助读者更好地理解和使用这些连接方式。

·后端开发

Weex 跨平台移动开发框架:核心特性与使用指南

Weex 是由阿里巴巴开源的跨平台移动开发框架,支持使用 Vue.js 或 Rax 构建高性能的 iOS、Android 和 Web 应用。本文详细解析了 Weex 的核心特性、架构、工作流程、组件和模块、开发工具、优缺点、应用场景及未来发展。

·前端开发

ECharts 与 DataV 数据可视化工具对比分析 | 选择指南

本文详细对比了 ECharts 和 DataV 两个常用的数据可视化工具,包括它们的设计目标、优缺点、使用场景和技术栈,帮助读者根据具体需求选择合适的工具。

·前端开发

前端部署后通知用户刷新页面的常见方案 | 单页应用更新提示

本文介绍了在前端部署后通知用户刷新页面的几种常见方案,包括WebSocket实时通知、轮询检查版本、Service Worker版本控制、版本号对比、自动刷新、使用框架内置功能以及第三方库。每种方案的优缺点和示例代码均有详细说明。

·前端开发

TypeScript 映射类型常见问题与解决方案 | 提升代码维护性

本文探讨了在使用 TypeScript 时,映射类型的不当使用可能导致的问题,如代码难以维护、类型推断不准确或性能问题,并提供了相应的解决方案和最佳实践。

·编程语言