剑指 Offer 40. 最小的k个数

题目:

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

解答:

快速排序方法:

思路:

使用快排给目标数组排序,然后返回前k个数字。

代码:

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int[] temp=new int[k];
        quickSort(arr,0,arr.length-1);
        for(int i=0;i<k;i++)
            temp[i]=arr[i];
        return temp;
    }
    public void quickSort(int[] arr,int left,int right){
        if(left<right){
            int l=left,r=right;
            int temp=arr[left];
            while(l<r){
                while(l<r&&arr[r]>temp) r--;
                while(l<r&&arr[l]<=temp)    l++;
                int i=arr[r];
                arr[r]=arr[l];
                arr[l]=i;
            }
            arr[left]=arr[l];
            arr[l]=temp;
            quickSort(arr,left,l-1);
            quickSort(arr,l+1,right);
        }
    }
}

使用PriorityQueue大根堆(最慢):

思路:

  • 使用堆数据结构来辅助得到最小的 k 个数。堆的性质是每次可以找出最大或最小的元素。我们可以使用一个大小为 k 的最大堆(大顶堆),将数组中的元素依次入堆,当堆的大小超过 k 时,便将多出的元素从堆顶弹出。
  • 这样,由于每次从堆顶弹出的数都是堆中最大的,最小的 k 个元素一定会留在堆里。这样,把数组中的元素全部入堆之后,堆中剩下的 k 个元素就是最大的 k 个数了。

代码:

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
    if (k == 0) {
        return new int[0];
    }
    // 使用一个最大堆(大顶堆)
    // Java 的 PriorityQueue 默认是小顶堆,添加参数使其变成最大堆
    Queue<Integer> heap = new PriorityQueue<>(k, (x,y) -> (y-x));
    for (int e : arr) {
        // 当前数字小于堆顶元素才会入堆
        if (heap.isEmpty() || heap.size() < k || e < heap.peek()) {
            heap.offer(e);
        }
        if (heap.size() > k) {
            heap.poll(); // 删除堆顶最大元素
        }
    }

    // 将堆中的元素存入数组
    int[] res = new int[heap.size()];
    int j = 0;
    for (int e : heap) {
        res[j++] = e;
    }
    return res;
}
}

快排变形(最快):

思路:

  • 题目只要求返回最小的 k 个数,对这 k 个数的顺序并没有要求。因此,只需要将数组划分为 最小的 k 个数 和 其他数字 两部分即可,而快速排序的哨兵划分可完成此目标。
  • 根据快速排序原理,如果某次哨兵划分后 基准数正好是第 k 小的数字 ,那么此时基准数左边的所有数字包括基数便是题目所求的 最小的 k 个数 。
  • 根据此思路,考虑在每次哨兵划分后,判断基准数在数组中的索引是否等于 k ,若 true则直接返回此时数组的前 k 个数字即可。

代码:

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        // 最后一个参数表示我们要找的是下标为k-1的数
        return quickSearch(arr, 0, arr.length - 1, k - 1);
    }

    private int[] quickSearch(int[] nums, int lo, int hi, int k) {
        // 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
        int j = partition(nums, lo, hi);
        if (j == k) {
            return Arrays.copyOf(nums, j + 1);
        }
        // 否则根据下标j与k的大小关系来决定继续切分左段还是右段。
        return j > k? quickSearch(nums, lo, j - 1, k): quickSearch(nums, j + 1, hi, k);
    }

    // 快排切分,返回下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边。
    private int partition(int[] nums, int lo, int hi) {
        int v = nums[lo];
        int i = lo, j = hi + 1;
        while (true) {
            while (++i <= hi && nums[i] < v);
            while (--j >= lo && nums[j] > v);
            if (i >= j) {
                break;
            }
            int t = nums[j];
            nums[j] = nums[i];
            nums[i] = t;
        }
        nums[lo] = nums[j];
        nums[j] = v;
        return j;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值