题目FindKthMin

在一个无序数组中怎么找到第k小的数, k从1开始, 问怎么O(N)拿下

笔试用的方法

大根堆

  1. // 利用大根堆,时间复杂度O(N*logK)
  2. public static int minKth(int[] arr, int k) {
  3. PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> {
  4. return o2 - o1;
  5. });
  6. for (int i = 0; i < k; i++) {
  7. maxHeap.add(arr[i]);
  8. }
  9. for (int i = k; i < arr.length; i++) {
  10. if (arr[i] < maxHeap.peek()) {
  11. maxHeap.poll();
  12. maxHeap.offer(arr[i]);
  13. }
  14. }
  15. return maxHeap.peek();
  16. }

借鉴快排思路

借用快排的思路,但又不是快排
image.png
快排的做法是,随机选一个数m,不动,然后左边去递归,右边去递归,
而这里我们是只会进左右两侧的某一侧的,
image.png

  1. // 改写快排,时间复杂度O(N)
  2. public static int minKth2(int[] arr, int k) {
  3. return process2(arr, 0, arr.length - 1, k - 1);
  4. }
  5. // arr 第k小的数
  6. // process2(arr, 0, N-1, k-1)
  7. // arr[L..R] 范围上,如果排序的话(不是真的去排序),找位于index的数
  8. // index [L..R]
  9. public static int process2(int[] arr, int L, int R, int index) {
  10. if (L == R) {
  11. return arr[L];
  12. }
  13. int pivot = arr[L + (int) (Math.random() * (R - L + 1))];
  14. int[] range = partition(arr, L, R, pivot);
  15. if (index >= range[0] && index <= range[1]) {
  16. return arr[index];
  17. } else if (index < range[0]) {
  18. return process2(arr, L, range[0] - 1, index);
  19. } else {
  20. return process2(arr, range[1] + 1, R, index);
  21. }
  22. }
  23. private static int[] partition(int[] arr, int L, int R, int pivot) {
  24. int less = L - 1;
  25. int more = R + 1;
  26. int cur = L;
  27. while (cur < more) {
  28. if (arr[cur] < pivot) {
  29. swap(arr, ++less, cur++);
  30. } else if (arr[cur] > pivot) {
  31. swap(arr, cur, --more);
  32. } else {
  33. cur++;
  34. }
  35. }
  36. return new int[]{less + 1, more - 1};
  37. }
  38. private static void swap(int[] arr, int i, int j) {
  39. int tmp = arr[i];
  40. arr[i] = arr[j];
  41. arr[j] = tmp;
  42. }

bfprt算法(跟面试官吹牛逼用的)

不需要像快排那样的概率累加,我也能让你收敛到O(n), 我是用严格的流程

我们刚刚上面讲的算法流程是这样的, bfprt算法与它后面的流程全都一样,只是第一步怎么选一个数不一样
image.png
image.png

那么它是如何选出这个数m的呢?

1) 逻辑上每5个数分为一组 , 共有N / 5组
2)每个组内部单独排序一下,但组与组之间仍然是无序的,
3)取出每个组的中位数, 如果最后一个组不足5个,那就取上中位数或者下中位数,不要两个相加/2,
这些数组成 数组m[ ]
4) 找出数组m[ ]的中位数 , 递归调用bfprt(m, N / 10)
———> 这个结果就是我们要的数m了
image.png
看一下时间复杂度
image.png
现在就是确定一下T(?) 也就是第5步
image.png
如果我进m左侧的递归时,我如果可以在arr中直到最多有几个< m, 那么我就知道我递归的规模有多大了。
所以我们可以这样反推 N - 大于等于m的至少有几个数 = 最多有几个 < m

image.png

m数组就是[a, b, c, d, e]
要选出这个数组中的第三小,假设是e,
image.png
有N / 10个数比e大, 对应回原来的数组中,比a大的还有2个,比c大的也有2个,
所以总共有 N / 10 * 3 = 十分之三N 的规模比e大, 那么就有十分之七N规模比e小,那么左侧递归的规模不就知道了么

image.png
所以全部时间复杂度就求出来了 O(N)

  1. // arr[L..R] 如果排序的话,位于index位置的数,是什么,返回
  2. private static int bfprt(int[] arr, int L, int R, int index) {
  3. if (L == R) {
  4. return arr[L];
  5. }
  6. int pivot = medianOfMedians(arr, L, R);
  7. int[] range = partition(arr, L, R, pivot);
  8. if (index >= range[0] && index <= range[1]) {
  9. return arr[index];
  10. } else if (index < range[0]) {
  11. return bfprt(arr, L, range[0] - 1, index);
  12. } else {
  13. return bfprt(arr, range[1] + 1, R, index);
  14. }
  15. }
  16. // arr[L...R] 五个数一组
  17. // 每个小组内部排序
  18. // 每个小组中位数领出来,组成marr
  19. // marr中的中位数,返回
  20. private static int medianOfMedians(int[] arr, int L, int R) {
  21. int size = R - L + 1;
  22. int offset = size % 5 == 0 ? 0 : 1;
  23. int[] mArr = new int[size / 5 + offset];
  24. for (int team = 0; team < mArr.length; team++) {
  25. int teamFirst = L + team * 5;
  26. // L ... L + 4
  27. // L +5 ... L +9
  28. // L +10....L+14
  29. mArr[team] = getMedian(arr, teamFirst, Math.min(R, teamFirst + 4));
  30. }
  31. // marr中,找到中位数
  32. // marr(0, marr.len - 1, mArr.length / 2 )
  33. return bfprt(mArr, 0, mArr.length - 1, mArr.length / 2);
  34. }
  35. private static int getMedian(int[] arr, int L, int R) {
  36. insertionSort(arr, L, R);
  37. return arr[(L + R) / 2];
  38. }
  39. private static void insertionSort(int[] arr, int L , int R) {
  40. for (int begin = L + 1; begin <= R; begin++) {
  41. int cur = L;
  42. while (cur > 0 && arr[cur] < arr[cur - 1]) {
  43. int tmp = arr[cur];
  44. arr[cur] = arr[cur - 1];
  45. arr[cur - 1] = tmp;
  46. cur--;
  47. }
  48. }
  49. }