topk问题三种解决办法(包含二分法解法)!

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;	
	}
}


猜你喜欢

转载自blog.csdn.net/YeBobr/article/details/80043126