40.最小的K个数

40. 最小的K个数

1 题目描述

​ 输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

2 题目分析

最小的k个数,直观思路就是先排序然后找出前k个,这种解法当然不行,下面介绍两种海量数据中的topK问题思路:
1. 用大顶堆:首先将前k个元素加入大顶堆中,然后从第k+1个开始将当前元素和堆顶元素比较,把较小者加入堆中(Java中用优先队列实现,如果不需要自己手动写堆的话)
2. 快排partition思想:以基准pivot将数组划分为两部分,左边一部分小于等于pivot,右边一部分大于pivot;这里与快排不同的是快排需要对左边和右边两部分都要处理,
    这里只需要处理一边即可。具体如下:
    定义一个partition(arr, start, end, k)表示划分数组arr的[start,end]部分,使前k个小的数在数组的左侧,假设划分返回的下标为pos,即pivot是数组中第
    pos-l+1小的数,那么会有三种情况:
    * 如果pos-l+1==k,表示pivot就是第k小的数,直接返回即可。
    * 若果pos-l+1<k,表示第k小的数在pivot的右侧,因此递归调用partition(arr, pos + 1, end, k-(pos - l + 1))
    * 如果post-l+1>k,表示第k小的数在pivot的左侧,递归调用partition(arr, start, pos - 1, k)。
   递归入口:partition(arr, 0, arr.length - 1 ,k)
   > 注意:pivot的选择常见的有三种1. 选择l作为基准;2. 选择r作为基准;3. 选择一个随机索引与l或者r交换,将l或者r作为基准。
   > 本题采用基于随机的划分,与l交换

3 代码

  • 大顶堆

    public int[] getLeastNumbers(int[] arr, int k) {
        int[] ans = new int[k];
        if (k == 0) return ans;
        // 创建大顶堆
        PriorityQueue<Integer> pq = new PriorityQueue<>((x, y) ->(y - x));
        // 先将前k个加入大顶堆中
        for (int i = 0; i < k; i++) {
            pq.offer(arr[i]);
        }
    
        // 从第k+1个开始比较当前元素和堆顶元素
        int n = arr.length;
        for (int i = k; i < n; i++) {
            if (pq.peek() > arr[i]) {
                pq.poll();
                pq.offer(arr[i]);
            }
        }
        for (int i = 0; i < k; i++) {
            ans[i] = pq.poll();
        }
        return ans;
    }
    
  • 随机快速选择

    public int[] getLeastNumbers1(int[] arr, int k) {
        // 递归入口
        randomSelected(arr, 0, arr.length - 1, k);
        // 创建大小为k的数组存储前k个元素
        int[] ans = new int[k];
        for (int i = 0; i < k; i++) {
            ans[i] = arr[i];
        }
        return ans;
    }
    
    private void randomSelected(int[] arr, int start, int end, int k) {
        // 递归出口
        if (start >= end) return;
    
        int pos = randomPartition(arr, start, end);
        int num = pos - start + 1;  // 表示第num小的元素
        if (k == num) return;
        else if (k < num) {
            randomSelected(arr, start, pos - 1, k);
        } else {
            randomSelected(arr, pos + 1, end, k - num);
        }
    }
    
    private int randomPartition(int[] arr, int start, int end) {
        // 生成一个随机索引号
        int i = new Random().nextInt(end - start + 1) + start;
        swap(arr, end, i);
        return parition(arr, start, end);
    }
    
    private int parition(int[] arr, int start, int end) {
        // 取最右边元素为基准
        int pivot = arr[end];
        int index = start - 1;
        for (int i = start; i <= end - 1; i++) {
            if (arr[i] <= pivot) {
                index++;
                swap(arr, index, i);
            }
        }
        swap(arr, end, index + 1);
        return index + 1;
    }
    
    public void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值