• 剑指 Offer 40. 最小的k个数【查找排序】


    难度等级:简单

    上一篇算法:

    剑指 Offer 44. 数字序列中某一位的数字【数学】

    力扣此题地址:

    剑指 Offer 40. 最小的k个数 - 力扣(LeetCode)

    1.题目:最小的k个数

    输入整数数组 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.解题思路:

    思路:快速排序加判断

    找出其中最小的 k 个数,这句话隐藏着以下几个意思:

    • 1、找出的这 k 个数并不需要按照顺序排列。
    • 2、如果一开始就知道某个数不在这 k 个数中,完全可以将它丢到一旁。

    也就意味着,在排序过程中,我们可以去不断的缩小排序的区间,这里我们借助快速排序的代码,稍微的改动几行就完成了这道题目。

    具体操作如下:

    • 1、以当前区间的第一个元素为基准元素 pivot,根据快速排序的操作,将当前区间划分为了三个区间。
      • 1、左侧区间均是小于等于基准元素 pivot 的元素
      • 2、中间区间均是等于基准元素 pivot 的元素
      • 3、右侧区间均是大于等于基准元素 pivot 的元素
    • 2、对比基准元素 pivot 所在的下标 index 与 k 的关系
      • 1、index 小于 k,说明从 0 到 index 这个左侧区间中的元素不足 k 个,那么最小的 k 个数肯定部分是在这个区间,还需要继续在右侧区间中去寻找出一部分元素来填充,因此对对右侧区间进行快速排序即可
      • 2、index 等于 k,说明从 0 到 index 这个区间中的所有元素就是那些最小的 k 个数,将其返回。
      • 3、index 大于 k,说明从 0 到 index 这个左侧区间中的元素超过了 k 个,那么最小的 k 个数肯定是都在在这个区间,而中间、右侧区间均可以不去处理,只需要继续对左侧区间进行快速排序即可,找到那 k 个数。

    3.代码实现:

    1. class Solution {
    2. public int[] getLeastNumbers(int[] arr, int k) {
    3. if (k == 0 || arr.length == 0) {
    4. return new int[0];
    5. }
    6. // 执行快速排序操作,定位找到下标为 k - 1 的那个元素
    7. return quickSort(arr,0,arr.length - 1,k - 1);
    8. }
    9. // 函数传入待排序数组 nums
    10. // 排序区间的左端点 left
    11. // 排序区间的右端点 right
    12. private int[] quickSort(int[] nums,int left, int right , int index){
    13. // 调用函数 partition,将 left 和 right 之间的元素划分为左右两部分
    14. int mid = partition(nums,left,right);
    15. // 如果 mid 下标恰巧为 index,那么找到了最小的 k 个数
    16. if (mid == index) {
    17. // 直接返回
    18. return Arrays.copyOf(nums, mid + 1);
    19. // 如果 mid 下标大于 index,那么说明需要在左侧元素中去切分
    20. }else if( mid > index ){
    21. // 对 mid 左侧的元素进行快速排序
    22. return quickSort(nums,left,mid - 1, index );
    23. }else{
    24. // 对 mid 右侧的元素进行快速排序
    25. return quickSort(nums,mid + 1,right, index );
    26. }
    27. }
    28. private int partition(int[] nums, int left ,int right){
    29. // 经典快速排序的写法
    30. // 设置当前区间的第一个元素为基准元素
    31. int pivot = nums[left];
    32. // left 向右移动,right 向左移动,直到 left 和 right 指向同一元素为止
    33. while( left < right ){
    34. // 只有当遇到小于 pivot 的元素时,right 才停止移动
    35. // 此时,right 指向了一个小于 pivot 的元素,这个元素不在它该在的位置上
    36. while( left < right && nums[right] >= pivot ){
    37. // 如果 right 指向的元素是大于 pivot 的,那么
    38. // right 不断的向左移动
    39. right--;
    40. }
    41. // 将此时的 nums[left] 赋值为 nums[right]
    42. // 执行完这个操作,比 pivot 小的这个元素被移动到了左侧
    43. nums[left] = nums[right];
    44. // 只有当遇到大于 pivot left 才停止移动
    45. // 此时,left 指向了一个大于 pivot 的元素,这个元素不在它该在的位置上
    46. while( left < right && nums[left] <= pivot){
    47. // 如果 left 指向的元素是小于 pivot 的,那么
    48. // left 不断的向右移动
    49. left++;
    50. }
    51. // 将此时的 nums[right] 赋值为 nums[left]
    52. // 执行完这个操作,比 pivot 大的这个元素被移动到了右侧
    53. nums[right] = nums[left];
    54. }
    55. // 此时,left 和 right 相遇,那么需要将此时的元素设置为 pivot
    56. // 这个时候,pivot 的左侧元素都小于它,右侧元素都大于它
    57. nums[left] = pivot;
    58. // 返回 left
    59. return left;
    60. }
    61. }
  • 相关阅读:
    零基础自学SQL课程 | 相关子查询
    读写分离后,性能居然提升100%了呀
    【leetcode】【2022/8/25】658. 找到 K 个最接近的元素
    《Effective C++》《构造/析构/赋值运算——9、绝不在构造和析构过程中调用virtual函数》
    基于Django的博客系统之增加手机验证码登录(九)
    Unity Profiler 详细解析(一)
    详细堆排序的实现
    短视频如何展现效果更佳?不用类型的短视频有不同的侧重点
    MYSQL数据库学习
    tiup cluster stop
  • 原文地址:https://blog.csdn.net/m0_51697147/article/details/127547700