当前位置: 首页 > news >正文

面试常问:如何在一个长度为n的无序数据快速获取前k个数值

1. 场景引入

在面试中,我们经常会遇到类似的问题:

“给定一个长度为 n 的无序数组,如何快速找出其中前 k 个最大值?”

初看之下,这似乎只需排序即可解决。但当面对亿级甚至更大规模的数据时,全量排序(O(n log n))的成本极高,不现实。在这种大数据背景下,如何在时间复杂度和空间复杂度之间权衡,高效找出前 k 个数值,成为考察我们算法功力的关键点。


2. 传统排序的局限与优化思路

很多人在遇到这类题目时,第一反应是“全量排序取前 k 大值”,比如直接用Arrays.sort()。但如果数组长度达到了 10910^9109,排序的时间和空间消耗你恐怕很难接受。

这类问题在算法面试中广泛存在,既考察你对数据结构的理解,也检验你在大规模数据下的优化能力。本文会带你从最直观的排序方案,逐步过渡到小顶堆、快速选择等更高效的做法,帮你理解如何真正搞定“如何高效获取无序数组的前 k 个值”这个核心问题。


3. 场景拓展与问题分析

其实在实际业务中,找“Top K”非常常见:

  • 电商推荐:找出销量排名前100的商品
  • 日志分析:统计访问量最高的IP
  • 搜索引擎:返回相关性得分最高的前10条记录

你不仅在面试中会被问到,在实际开发也常常遇到:

“在长度为 n 的无序数组中,如何快速找到前 k 个数?”


4. 堆排序:解决“Top K”更高效

全量排序问题的本质在于,我们只关心前k个值,没必要排序所有元素。这里,小顶堆(或者大顶堆,针对最小k值)是高效解法之一。核心思想是:

  • 构建一个大小为 k 的优先队列(最小堆或最大堆)。
  • 依次遍历每个元素:
    • 若堆未满,直接加入;
    • 若堆已满,且当前元素比堆顶大(对前 K 大值场景),则弹出堆顶,加入新元素;
  • 最终堆内存放的就是前 k 个最大值。

Java 实现代码:

1、堆排序(获取前k个最大值或最小值)

获取数组中前k个最大值

// 获取数组中前k个最大值
public int[] searchTopK(int[] nums, int k) {// 创建一个容量为k的小根堆(默认是小根堆)PriorityQueue<Integer> queue = new PriorityQueue<>(k);// 遍历数组中的每个元素for (int num : nums) {if (queue.size() < k) {// 如果堆还没有装满,就直接加入元素queue.offer(num);} else if (num > queue.peek()) {// 如果堆已满,且当前元素比堆顶(最小的元素)大// 移除堆顶(最小值),然后加入当前元素queue.poll();queue.offer(num);}}// 将堆中的元素转移到结果数组中(逆序取出)int[] result = new int[k];for (int i = k - 1; i >= 0; i--) {result[i] = queue.poll();}return result;
}

获取数组中前k个最小值


// 获取数组中前k个最小值
public static int[] searchTopK2(int[] nums, int k) {// 特殊情况处理:数组为空或k为0if (nums.length == 0 || k == 0) {return new int[]{};}// 创建容量为k的大根堆(自定义比较器,堆顶为最大值)PriorityQueue<Integer> queue = new PriorityQueue<>(k, (a, b) -> b - a);// 遍历数组for (int i = 0; i < nums.length; i++) {if (queue.size() < k) {// 堆未满,直接加入元素queue.offer(nums[i]);} else if(queue.peek() > nums[i]){// 如果当前元素比堆顶(最大值)小,则替换    queue.poll(); // 移除最大值queue.offer(nums[i]); // 插入较小的元素}}}// 将堆中的元素转为数组(逆序)int[] res = new int[k];for (int i = k - 1; i >= 0; i--) {res[i] = queue.poll();}return res;
}

时间复杂度: 建堆和维护堆:O(n log k)
空间复杂度: O(k)

2、获取第k个最大值

//获取第k大的最大值public static int findKthLargest(int[] nums, int k) {//创建小根堆PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);for (int num : nums) {if (minHeap.size() < k) {minHeap.offer(num); // 堆未满,直接放} else if (num > minHeap.peek()) {minHeap.poll();      // 弹出最小值minHeap.offer(num);  // 放入新值}}return minHeap.peek(); // 堆顶就是第k大}
//获取第k个最小值public static int findKthSmallest(int[] nums, int k) {//创建大根堆PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k,(a,b)->b-a);for (int num : nums) {if (maxHeap .size() < k) {maxHeap.offer(num); // 堆未满,直接放} else if (num < maxHeap.peek()) {maxHeap.poll();      // 弹出最小值maxHeap.offer(num);  // 放入新值}}return maxHeap.peek(); // 堆顶就是第k小}

3、查找top3最大值

public static void  searchTop3(int[]nums){// 初始化前三大值为最小整数Integer a = null, b = null, c = null;for (int num : nums) {// 跳过已经存在的值,确保去重if (num == a || num == b || num == c) {continue;}if (a == null || num > a) {c = b;b = a;a = num;} else if (b == null || num > b) {c = b;b = num;} else if (c == null || num > c) {c = num;}}System.out.println("a="+a+",b="+b+",c="+c);}

时间复杂度:O(n)
空间复杂度:O(1)


4、PriorityQueue介绍

概述

  • PriorityQueue是Java中的一个基于堆(Heap)实现的优先队列。
  • 元素会根据**优先级(自然顺序或自定义比较器)**自动排序。
  • 常用场景:需要不断获取当前最小或最大元素的队列。

底层实现

  • 基于堆(Heap):通常是完全二叉树,用数组存储。
  • 堆性质
    • 最小堆(Min-Heap):堆顶(根节点)是最小元素。
    • 最大堆(Max-Heap):堆顶是最大元素(通过自定义比较器实现)。
  • 操作offer()poll()操作时间复杂度为O(log n)

主要方法

方法描述复杂度
offer(e)插入元素 e,确保堆性质保持O(log n)
poll()移除并返回堆顶元素(最小或最大)O(log n)
peek()查看堆顶元素(不移除)O(1)
size()获取元素个数O(1)
isEmpty()判断队列是否为空O(1)

默认排序

  • 默认(无参数构造)
    • 是一个最小堆,元素根据元素的自然顺序(比如整数的从小到大)排序。
    • peek()返回最小元素。
  • 示例
    PriorityQueue<Integer> pq = new PriorityQueue<>();
    pq.offer(5);
    pq.offer(2);
    pq.offer(8);
    System.out.println(pq.peek()); // 输出 2
    

自定义排序

  • 通过传入比较器Comparator),可以实现最大堆或其他排序策略。

1. 最大堆(大根堆)

PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a,b)->b-a);
maxHeap.offer(5);
maxHeap.offer(2);
maxHeap.offer(8);
System.out.println(maxHeap.peek()); // 输出 8

2. 自定义比较器

PriorityQueue<Person> customQueue = new PriorityQueue<>((Person p1, Person p2) -> p2.getAge() - p1.getAge());
http://www.dtcms.com/a/279846.html

相关文章:

  • 网络传输过程
  • GaussDB between的用法
  • 光伏板如何最大化铺设?
  • 【PostgreSQL异常解决】`PostgreSQL`异常之类型转换错误
  • 记录自己在将python文件变成可访问库文件是碰到的问题
  • vert.x 官网docs, vert.x中文文档地址 vertx文档
  • 文心4.5开源之路:引领技术开放新时代!
  • 【前端:Typst】--let关键字的用法
  • 高德开放平台携手阿里云,面向开发者推出地图服务产品MCP Server
  • 外部协作不力影响项目进度,如何加强外部沟通
  • 项目进度压缩影响质量,如何平衡进度与质量
  • LeetCode|Day11|557. 反转字符串中的单词 III|Python刷题笔记
  • 稀土化合物:助力高效种植与健康养殖
  • vue笔记3 VueRouter VueX详细讲解
  • 对象的使用
  • CAN终端电阻为什么是60R+60R,而不直接用120R?
  • 前端vue对接海康摄像头流程
  • Flink窗口处理函数
  • C++-linux 5.gdb调试工具
  • 【从语言幻觉看趋势】从语言幻觉到多智能体协作:GPT多角色系统的技术演进与实践路径
  • 判断端口处于监听状态的方法
  • 腾讯云WAF域名分级防护实战笔记
  • EPLAN 电气制图(八):宏应用与变频器控制回路绘制全攻略
  • ssm学习笔记day07mybatis
  • 如何在 Shopify 中创建退货标签
  • 【C语言】浮点数在内存中的存储:从科学计数法到内存存储
  • 从输入URL到页面呈现都发生了什么?
  • MFC UI大小改变与自适应
  • wpf 实现窗口点击关闭按钮时 ​​隐藏​​ 而不是真正关闭,并且只有当 ​​父窗口关闭时才真正退出​​ 、父子窗口顺序控制与资源安全释放​
  • AI 优化大前端动画性能:流畅性与资源消耗的平衡