题目描述

image.png

解题思路

归并排序

  1. public int[] getLeastNumbers(int[] arr, int k) {
  2. // 归并排序
  3. mergeSort(arr, 0, arr.length - 1);
  4. return Arrays.copyOf(arr, k);
  5. }
  6. /**
  7. * 归并排序
  8. *
  9. * @param nums
  10. * @param l
  11. * @param r
  12. */
  13. public void mergeSort(int[] nums, int l, int r) {
  14. if (l >= r) {
  15. return;
  16. }
  17. int mid = (l + r) / 2;
  18. mergeSort(nums, l, mid);
  19. mergeSort(nums, mid + 1, r);
  20. int[] tempArray = new int[r - l + 1];
  21. for (int i = l; i <= r; i++) {
  22. tempArray[i - l] = nums[i];
  23. }
  24. int i = 0, j = mid - l + 1; // 分别指向左右子数组的首个元素
  25. for (int k = l; k <= r; k++) {
  26. if (i == mid - l + 1) {
  27. nums[k] = tempArray[j++];
  28. } else if (j == r || tempArray[j] >= tempArray[i]) {
  29. nums[k] = tempArray[i++];
  30. } else if (tempArray[j] < tempArray[i]) {
  31. nums[k] = tempArray[j++];
  32. }
  33. }
  34. }

快速排序

  1. public int[] getLeastNumbers(int[] arr, int k) {
  2. // 归并排序
  3. // mergeSort(arr, 0, arr.length - 1);
  4. // 快排
  5. quickSort(arr,k, 0, arr.length - 1);
  6. return Arrays.copyOf(arr, k);
  7. }
  8. /**
  9. * 快速排序
  10. *
  11. * @param arr
  12. * @param l
  13. * @param r
  14. */
  15. public void quickSort(int[] arr, int l, int r) {
  16. if (l >= r) {
  17. return;
  18. }
  19. int i = l, j = r;
  20. while (i < j) {
  21. while (i < j && arr[j] >= arr[l]) j--;
  22. while (i < j && arr[i] <= arr[l]) i++;
  23. swap(arr, i, j);
  24. }
  25. swap(arr, l, i); // 交换最后位置
  26. // 递归左右数组进行快速排序
  27. quickSort(arr, l, i - 1);
  28. quickSort(arr, i + 1, r);
  29. }
  30. public void swap(int[] arr, int i, int j) {
  31. int temp = arr[i];
  32. arr[i] = arr[j];
  33. arr[j] = temp;
  34. }

基于快速排序的数组划分

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

步骤

哨兵划分
划分完毕后,基准数为 arr[i] ,左 / 右子数组区间分别为 [l, i - 1][l,i−1] , [i + 1, r][i+1,r] ;
递归或返回

  • 若 k < i ,代表第 k + 1 小的数字在 左子数组 中,则递归左子数组;
  • 若 k > i ,代表第 k + 1 小的数字在 右子数组 中,则递归右子数组;
  • 若 k = i ,代表此时 arr[k] 即为第 k + 1 小的数字,则直接返回数组前 k 个数字即可;

也就是找到哨兵和k的关系,如果哨兵等于 k+1 , 那么前面的k个数都是小于哨兵的,因为不需要返回k个数都是有序,所以直接返回前面的k个数。

  1. /**
  2. * 基于快速排序的数组划分
  3. *
  4. * @param arr
  5. * @param l
  6. * @param r
  7. */
  8. public void quickSort(int[] arr, int k, int l, int r) {
  9. if (l >= r) {
  10. return;
  11. }
  12. int i = l, j = r;
  13. while (i < j) {
  14. while (i < j && arr[j] >= arr[l]) j--;
  15. while (i < j && arr[i] <= arr[l]) i++;
  16. swap(arr, i, j);
  17. }
  18. swap(arr, l, i); // 交换最后位置
  19. if (i > k) quickSort(arr, k, l, i - 1); // i > k,表示需要的元素在左数组
  20. if (i < k)quickSort(arr, k, i + 1, r); // i < k,表示需要的元素在右数组
  21. // 相等表示左边就是 k 个元素,而基准此时就是 k + 1,所以直接返回前面k个数
  22. }