1.根据快排中使用的分治法partition函数进行判断。
A.如果partition返回值index<k,那么在索引index+1 -> high使用分治法。
B.如果partition返回值index>=k,那么在索引low -> index-1使用分治法。
C.直到index = k - 1,返回下标为k-1数字。
时间复杂度O(n),空间复杂度O(1)。但是这种方法改变了数组中数字的位置。。
public class Main { public static void topk1(int[] input, int k) { if(input.length == 0){ System.out.println("input.length == 0!!"); return; } if(k>input.length){ System.out.println("k > input.length!!"); return; } int index = partition(input, 0, input.length-1); while(index != k - 1){ if(index > k-1)index = partition(input, 0, index-1); if(index < k-1)index = partition(input, index+1, input.length-1); } for (int i = 0; i < k; i++) { System.out.println(input[i]); } } public static int partition(int[] input, int start ,int end) { int pivot = input[start]; int low = start, high = end; while(low < high){ while(low<high&&input[high]<=pivot)high--; if(low<high){ input[low] = input[high]; low++; } while(low<high&&input[low]>pivot)low++; if(low<high){ input[high] = input[low]; high--; } } input[low] = pivot; return low; } }
2.使用最小堆来寻找最大的K个数。
A.我们首先需要创建一个大小为K的堆,如果我们想要空间复杂度为O(1)的话,我们可以在原数组中的前k个空间建堆。但是这样的话我们改变了原始数组的数字的位置。如果我们不想改变原始数组中的数字的位置,我们可以创建一个K个大小的容器,但是空间复杂度是O(k)。
B.然后我们对从第k+1个数到最后一个数子的每一个数字 nums[i] 都和最小堆进行比较,如果大于堆顶元素就将堆顶元素用nums[i]替换。
C.调整最小堆。
时间复杂度O(nlogk),空间复杂度O(k)(不改变数组元素位置)或O(1)(改变数组元素位置)
public class Main { //---------------------------------最小堆----------------------------------------- public static void topk3(int[] input, int k) { if(input.length == 0){ System.out.println("input.length == 0!!"); return; } if(k>input.length){ System.out.println("k > input.length!!"); return; } buildMinHeap(input, k); for (int i = k; i < input.length; i++) { if(input[i] > input[0]){ swap(input, i, 0); MinHeapFixedDown(input, k, 0); } } for (int i = 0; i < k; i++) { System.out.println(input[i]); } } public static void buildMinHeap(int[] input, int k) { for(int i = k/2-1;i >= 0;i--){ MinHeapFixedDown(input, k, i); } } public static void MinHeapFixedDown(int[] input, int k, int i) { int left = 2*i+1, right = left + 1; if((left < k && input[i] > input[left]) || ((right < k && input[i] > input[right]))){ if(right < k &&input[left] > input[right]){ swap(input, i, right); MinHeapFixedDown(input, k, right); }else { swap(input, i, left); MinHeapFixedDown(input, k, left); } } } private static void swap(int[] input, int i, int j) { int temp = input[i]; input[i] = input[j]; input[j] = temp; } }
3.二分法寻找前K个数。
如果我们想既不改变数组元素位置,也不想开辟空间怎么办呢?我们可以使用二分法。
时间复杂度O(nlog(max-min)),空间复杂度O(1).
public class Main { public static void topk3(int[] input, int k) { //判断特殊的输入 if(input.length == 0){ System.out.println("input is null!!!"); return; } if(k>input.length){ System.out.println("k must be below length!!!"); return; } //找最大值和最小值 int low = Integer.MAX_VALUE; int high = Integer.MIN_VALUE; for (int i = 0; i < input.length; i++) { if(input[i] > high)high = input[i]; if(input[i] < low)low = input[i]; } //count用来计算从大于mid小于high的数字的个数 int count = 0; //二分法核心代码 while(true){ int mid = (low + high)/2; count = count(input, mid, high); if(count > k)low = mid+1; else if(count < k){ k = k - count; high =mid - 1; mid = low; } else { for (int i = 0; i < input.length; i++) { if(input[i]>=mid)System.out.println(input[i]); } return; } } } //计算数组中两个数字之间数字的个数 public static int count(int[] input, int start, int end) { int count = 0; for(int i = 0;i < input.length;i++) if(input[i] >= start && input[i]<= end) count++; return count; } }