40. 最小的 K 个数

NowCoder

解题思路

快速选择

  • 复杂度:O(N) + O(1)
  • 只有当允许修改数组元素时才可以使用

快速排序的 partition() 方法,会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j],且 a[j+1..h] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。

  1. public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
  2. ArrayList<Integer> ret = new ArrayList<>();
  3. if (k > nums.length || k <= 0)
  4. return ret;
  5. findKthSmallest(nums, k - 1);
  6. /* findKthSmallest 会改变数组,使得前 k 个数都是最小的 k 个数 */
  7. for (int i = 0; i < k; i++)
  8. ret.add(nums[i]);
  9. return ret;
  10. }
  11. public void findKthSmallest(int[] nums, int k) {
  12. int l = 0, h = nums.length - 1;
  13. while (l < h) {
  14. int j = partition(nums, l, h);
  15. if (j == k)
  16. break;
  17. if (j > k)
  18. h = j - 1;
  19. else
  20. l = j + 1;
  21. }
  22. }
  23. private int partition(int[] nums, int l, int h) {
  24. int p = nums[l]; /* 切分元素 */
  25. int i = l, j = h + 1;
  26. while (true) {
  27. while (i != h && nums[++i] < p) ;
  28. while (j != l && nums[--j] > p) ;
  29. if (i >= j)
  30. break;
  31. swap(nums, i, j);
  32. }
  33. swap(nums, l, j);
  34. return j;
  35. }
  36. private void swap(int[] nums, int i, int j) {
  37. int t = nums[i];
  38. nums[i] = nums[j];
  39. nums[j] = t;
  40. }

大小为 K 的最小堆

  • 复杂度:O(NlogK) + O(K)
  • 特别适合处理海量数据

应该使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。

维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。

public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
    if (k > nums.length || k <= 0)
        return new ArrayList<>();
    PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
    for (int num : nums) {
        maxHeap.add(num);
        if (maxHeap.size() > k)
            maxHeap.poll();
    }
    return new ArrayList<>(maxHeap);
}

40. 最小的 K 个数 - 图1