剑指 Offer 40. 最小的k个数

194 阅读3分钟

# 剑指 Offer 40. 最小的k个数

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

1、题目📑

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

实例1

输入:arr = [3,2,1], k = 2

输出:[1,2] 或者 [2,1]

实例2

输入:arr = [0,1,2,1], k = 1

输出:[0]

限制

  • 0 <= k <= arr.length <= 10000
  • 0 <= arr[i] <= 10000

2、思路🧠

方法一:快排排序遍历前k个

利用快速排序O(NlogN) 的时间复杂度进行排序,取数组中前k个元素,即可得到结果。

方法二:堆

使用JDK8自带的堆 PriorityQueue 默认是小根堆,需要重新compare 方法定义为大根堆。

大根堆声明:

PriorityQueue<Integer> pq = new PriorityQueue<>(arr.length, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return (o2 - o1) > 0 ? 1 : -1;
    }
});
  1. 特判:arr == null || k == 0 直接返回空的数组。

  2. 将前k个数组中的数字依次进入堆中

    • 当堆中的数字个数大于k个时,判断堆顶元素数字与下一个待进入堆元素的数字的大小
    • 如果堆顶元素数字比下一个待进入堆元素的数字大,则将其元素弹出,将新的元素入堆,在这过程中一直保持前k个元素为目前扫描到的最小元素。
  3. 直到扫描到最后一个元素位置,将堆中的前k个元素依次弹出,得到答案。

方法三:优化快速排序

首先,需要理解和明白快速排序的根本思想,才能对其进行灵活变换。

代码和快排代码一样,不过多了几行判断,所以对于根本思想来说,非常重要

  • if (i > k) quick_sort(q, l, j, k) i 对应的是每次二分的左下标,如果 ik 大的话,说明此时 ik 的左边,此时我们只需要将数组的前 j 进行排序,就结束
  • else quick_sort(q,j + 1, r, k) 如果 ki 大的话,说明此时 ik 的右边,说明此时需要的不够,需要继续对右边的进行二分,直到找到我们需要的再次进行 if 操作。

废话少说 ~~~~~ 上代码!

3、代码👨‍💻

第一次commit AC

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        quickSort(arr, 0, arr.length - 1);
        int res [] = new int[k];
        for(int i = 0; i < k; i++) {
            res[i] = arr[i];
        }
        return res;
    }
    public static void quickSort(int arr[], int l, int r) {
        if (l >= r) return ;
        int i = l - 1;
        int j = r + 1;
        int mid = arr[(l + r) >> 1];
        while(i < j) {
            do i++; while(arr[i] < mid);
            do j--; while(arr[j] > mid);
            if(i < j) swap(arr, i, j);
        }
        quickSort(arr, l, j);
        quickSort(arr, j + 1, r);
    }
    public static void swap(int arr[], int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

时间复杂度:O(NlogN) N为数组 arr 的长度

空间复杂度:O(N) 快速排序的递归深度平均为 O(logN) ,最差情况为 O(N)。

第二次commit AC

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if(arr == null || k == 0) {
            return new int [0];
        }
        PriorityQueue<Integer> pq = new PriorityQueue<>(arr.length, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return (o2 - o1) > 0 ? 1 : -1;
            }
        });

        for(int num : arr) {
            if(pq.size() < k) {
                pq.offer(num);
            }else if(pq.peek() > num) {
                pq.poll();
                pq.offer(num);
            }
        }

        int res[] = new int [k];
        for(int i = 0; i < k; i++) {
            res[i] = pq.poll();
        }
        return res;
    }
}

时间复杂度:O(N) N为堆的大小 K

空间复杂度:O(N log N) 入堆和出堆操作的时间复杂度均为 O(logk),每个元素都需要进行一次入堆操作。

第三次commit AC

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        quick_sort(arr, 0, arr.length - 1, k);
        int res [] = new int[k];
        for(int i = 0; i < k; i++) {
            res[i] = arr[i];
        }
        return res;
    }
    public static void quick_sort(int q[], int l, int r, int k) {
        if (l >=  r) return ;
        int i = l - 1;
        int j = r + 1;
        int mid = q[(l + r) >> 1];
        while(i < j){
            do i++; while (q[i] < mid); 
            do j--; while (q[j] > mid);
            if(i < j) swap(q, i, j);
        }
        if (i > k) quick_sort(q, l, j, k);
        else quick_sort(q,j + 1, r, k);
    }

    public static void swap(int arr[], int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

时间复杂度:O(N) N为数组 arr 的长度,每次二分一共有N + N/2 + N/4 + …… = 2N - 1 ,即时间复杂度为 O(N)。

空间复杂度:O(log N) 划分函数的平均递归深度为 O(log N)。 image-20220330223928371

4、总结

该题目数组,底层的数据结构要有了解,其次还要熟悉快排的底层思想,如何进行每一次的操作。熟悉堆的结构,在JDK8如果使用大根堆、小根堆多要熟练掌握。

快排模板:

public static void quick_sort(int q[], int l, int r, int k) {
        if (l >=  r) return ;
        int i = l - 1;
        int j = r + 1;
        int mid = q[(l + r) >> 1];
        while(i < j){
            do i++; while (q[i] < mid); 
            do j--; while (q[j] > mid);
            if(i < j) swap(q, i, j);
        }
        quick_sort(q, l, j, k);
        quick_sort(q,j + 1, r, k);
    }
​
    public static void swap(int arr[], int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

❤️‍来自专栏《LeetCode基础算法题》欢迎订阅❤️‍

厂长写博客目的初衷很简单,希望大家在学习的过程中少走弯路,多学一些东西,对自己有帮助的留下你的赞赞👍或者关注➕都是对我最大的支持,你的关注和点赞给厂长每天更文的动力。

对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!

原题链接:剑指 Offer 40. 最小的k个数