51.最小的k个数(209)
-
题目描述:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
-
思路:
方法一:使用快排的分区函数,只需获取第k小的元素即可获取最小的k个数。第k个位置的数以及之前的数就为最小的k个数。 此种方法会改变数组中元素的顺序。时间复杂度O(n)
方法二:维护一个大小为k的最大堆,然后将数组中的元素和最大堆的最大值比较大小,若比最大值小,则替换并重新构造最大堆。当数组中的元素都入堆完后,堆中的元素就为最小的K个数。时间复杂度O(nlogk)
-
代码:
package _51.最小的k个数; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; /** * 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。 * @author Administrator * */ public class GetLeastNumbers { /** * 方法一:会改变原数组中数字的顺序,并且获取的最小的k个数不一定是有序的。 * 使用快排的分区函数求第k小的数,那么k位置之前的数就是最小的k个数,但是不能保证这些数之间是有序的。 * @param input * @param k * @return */ public static ArrayList<Integer> getLeastNumbers_Solution1(int [] input, int k) { ArrayList<Integer> list = new ArrayList<>(); if(input == null || input.length == 0 || k <= 0) return list; int left = 0; int right = input.length - 1; int index = paritition(input,left,right); while(index != (k-1)){ if(index > (k-1)){ index = paritition(input,left,index-1); } else{ index = paritition(input,index+1,right); } } //循环结束后,第k-1处的数字已经确定,相应k-1以及之前的数字就是最小的k个数 for(int i = 0; i < k; i++){ list.add(input[i]); } return list; } /** * 快排的分区函数 * @param array * @param left * @param right * @return */ public static int paritition(int[] array,int left,int right){ int pivot = array[left]; while(left < right){ while((left<right) && array[right] >= pivot) right--; array[left] = array[right]; while((left<right) && array[left] < pivot) left++; array[right] = array[left]; } array[right] = pivot; return right; } /** * 方法二:维护一个大小为k的最大堆。不会改变原数组,获取的最小k个数之间也不是有序的。 * @param input * @param k * @return */ public static ArrayList<Integer> getLeastNumbers_Solution2(int [] input, int k) { ArrayList<Integer> list = new ArrayList<>(); if(input == null || input.length == 0 || k <= 0 || k > input.length) return list; int[] heap = new int[k]; //1、向堆中添加元素 for(int i = 0; i < k; i++){ heap[i] = input[i]; } //2、构造一个完全二叉树(将数组看成是完全二叉树的顺序存储) //3、初始化最大堆 for(int i = heap.length/2 - 1; i >= 0; i--){//从最后一个分支结点开始构造 shif(heap,i,heap.length-1); } //4.将数组中剩余元素和最大堆的最大值比较,如果比最大值小,则替换最大值并重新构造最大堆。 for(int i = k; i < input.length; i++){ if(input[i] < heap[0]){ heap[0] = input[i]; shif(heap,0,heap.length-1); } } //5.返回结果 for(int i = 0; i < heap.length; i++){ list.add(heap[i]); } return list; } /** * 根结点的左子树和右子树已是大根堆,将孩子的结点的最大值与根结点比较, * 若根结点较小,则交换,这有可能破坏下一级的堆,继续采用上述方法构造下一级的堆。 * @param array * @param low * @param high */ public static void shif(int[] array,int low,int high){ //数组元素是从0开始的:根结点i;左孩子结点2*i+1;右孩子结点2*i+2;最后一个分支结点 array.length/2-1 int i = low; int j = 2*i+1;//指向结点i的左孩子结点 while(j <= high){ if(j<high && array[j] < array[j+1]) j++; if(array[j] > array[i]){ array[i] = array[i] + array[j]; array[j] = array[i] - array[j]; array[i] = array[i] - array[j]; i = j; j = 2*i+1; } else break; } } public static void main(String[] args) { int[] array ={1,3,6,5,4,2}; ArrayList<Integer> list = getLeastNumbers_Solution2(array,6); System.out.println(list); } }