剑指offer-29、最⼩的k个数

题⽬描述

输⼊ n 个整数,找出其中最⼩的 K 个数。例如输⼊ 4,5,1,6,2,7,3,8 这 8 个数字,则最⼩的 4 个数字是 1,2,3,4 。

思路及解答

排序法

最直接的思路是将数组排序后取前k个元素

java 复制代码
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
    ArrayList<Integer> result = new ArrayList<>();
    if (input == null || k <= 0 || k > input.length) {
        return result; // 处理非法输入
    }
    Arrays.sort(input); // 使用Java内置排序,时间复杂度O(n log n)
    for (int i = 0; i < k; i++) {
        result.add(input[i]); // 取前k个元素
    }
    return result;
}
  • 时间复杂度 :主要由 Arrays.sort() 决定,为 O(n log n)
  • 空间复杂度:O(k),用于存储结果的列表

大顶堆法(推荐)

维护一个大小为k的大顶堆​(Max-Heap),堆顶是当前k个数中的最大值。遍历数组,如果当前数比堆顶小,则替换堆顶并调整堆

这⾥不展开最⼤堆和最⼩堆的具体讲解,什么是最⼤堆/最⼩堆呢?

  • ⼤/⼩堆树是完全⼆叉树
  • ⼤/⼩堆树的每⼀个节点不⼩于/不⼤于其孩⼦节点。
java 复制代码
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
    ArrayList<Integer> result = new ArrayList<>();
    if (input == null || k <= 0 || k > input.length) {
        return result;
    }
    // 创建一个大顶堆,使用自定义比较器实现降序排列
    PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
    for (int num : input) {
        if (maxHeap.size() < k) {
            maxHeap.offer(num); // 堆未满,直接加入
        } else if (num < maxHeap.peek()) { // 当前数比堆顶最大值小
            maxHeap.poll(); // 移除堆顶最大值
            maxHeap.offer(num); // 将当前数加入堆
        }
    }
    result.addAll(maxHeap);
    return result;
}
  • 时间复杂度:O(n log k)。每次插入或删除堆的操作需要 O(log k) 时间,共需进行 n 次这样的操作
  • 空间复杂度:O(k),堆的大小

堆这种数据结构适合处理海量数据 ​(数据无法一次性装入内存),或者当 ​k 远小于 n​ 时非常高效。

大数据问题处理面试题www.seven97.top/interview/i...

快速选择法(效率最优)

基于快速排序的 ​partition​ 操作。每次随机选择一个基准元素(pivot),将数组划分为比基准小和比基准大的两部分。根据基准的位置与k的关系,决定继续划分哪一部分

java 复制代码
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
    ArrayList<Integer> result = new ArrayList<>();
    if (input == null || k <= 0 || k > input.length) {
        return result;
    }
    quickSelect(input, 0, input.length - 1, k);
    for (int i = 0; i < k; i++) {
        result.add(input[i]);
    }
    return result;
}

private void quickSelect(int[] arr, int left, int right, int k) {
    if (left >= right) return;
    int pivotIndex = partition(arr, left, right);
    if (pivotIndex == k - 1) { // 基准点正好是第k-1个位置(0-indexed),则其左边就是最小的k个数
        return;
    } else if (pivotIndex > k - 1) { // 基准点位置大于k-1,说明最小的k个数在左边
        quickSelect(arr, left, pivotIndex - 1, k);
    } else { // 基准点位置小于k-1,说明最小的k个数还需要包括右边的一部分
        quickSelect(arr, pivotIndex + 1, right, k);
    }
}

// 分区操作,将小于基准的数放在左边,大于基准的数放在右边,返回基准的最终位置
private int partition(int[] arr, int left, int right) {
    // 随机选择基准,避免最坏情况
    int randomIndex = new Random().nextInt(right - left + 1) + left;
    swap(arr, left, randomIndex);
    int pivot = arr[left];
    int i = left, j = right;
    while (i < j) {
        while (i < j && arr[j] >= pivot) j--;
        while (i < j && arr[i] <= pivot) i++;
        if (i < j) swap(arr, i, j);
    }
    swap(arr, left, i);
    return i;
}

private void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
  • 时间复杂度平均 O(n)​。每次partition操作平均处理n个元素,但每次都能将问题规模大致减半(n + n/2 + n/4 + ... ≈ 2n)。最坏情况(每次选的基准都是最大或最小值)下为O(n²),但随机选择基准有效避免了最坏情况的发生。
  • 空间复杂度:O(log n),递归调用栈的深度
相关推荐
FQNmxDG4S14 小时前
Java多线程编程:Thread与Runnable的并发控制
java·开发语言
虹科网络安全15 小时前
艾体宝干货|数据复制详解:类型、原理与适用场景
java·开发语言·数据库
axng pmje15 小时前
Java语法进阶
java·开发语言·jvm
rKWP8gKv716 小时前
Java微服务性能监控:Prometheus与Grafana集成方案
java·微服务·prometheus
老前端的功夫16 小时前
【Java从入门到入土】28:Stream API:告别for循环的新时代
java·开发语言·python
qq_4352879216 小时前
第9章 夸父逐日与后羿射日:死循环与进程终止?十个太阳同时值班的并行冲突
java·开发语言·git·死循环·进程终止·并行冲突·夸父逐日
小江的记录本16 小时前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
yaoxin52112316 小时前
397. Java 文件操作基础 - 创建常规文件与临时文件
java·开发语言·python
极客先躯18 小时前
高级java每日一道面试题-2025年11月24日-容器与虚拟化题[Dockerj]-runc 的作用是什么?
java·oci 的命令行工具·最小可用·无守护进程·完全标准·创建容器的核心流程·runc 核心职责思维导图
用户606487671889618 小时前
AI 抢不走的技能:用 Claude API 构建自动化工作流实战
java