输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
代码
解法一
先对数组排序,再取出k个元素,时间复杂度等于排序方法的复杂度,会改变原数组,但是取出的k个元素是有序的
public static int[] findLeastK(int[] array, int k) {
if (array == null || array.length == 0 || k <= 0) {
return null;
}
sort(array, 0, array.length - 1);
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = array[i];
}
return result;
}
/**
* 快排
* @param array
* @param lo
* @param hi
*/
public static void sort(int[] array,int lo ,int hi){
if(lo>=hi){
return ;
}
int index=partition(array,lo,hi);
sort(array,lo,index-1);
sort(array,index+1,hi);
}
public static int partition(int []array,int start,int end){
// 固定的切分方式
int key = array[start];
while(start < end){
// 从后半部分向前扫描
while(end > start && array[end] >= key){
end--;
}
array[start] = array[end];
// 从前半部分向后扫描
while(start < end && array[start] < key){
start++;
}
array[end]=array[start];
}
array[start]=key;
return end;
}
public static void main(String[] args) {
int[] array = {4, 5, 1, 6, 2, 7, 3, 8};
int[] result = findLeastK(array, 4);
for (int i : result) {
System.out.print(i + " ");
}
}
输出是 1 2 3 4
解法二
求数组的最小K个数,可以理解为求topK(最小堆),采用最大堆,适用于大数据量小K的情况,O(n*logk),不会改变原数组,但是结果是无序的
/**
* 最大堆
*/
static class MaxHeap {
// 堆的存储结构 - 数组,father=data[2 * i],left=data[2 * i +1], right=data[2 * i + 2]
private int[] data;
/**
* 将一个数组传入构造方法,并转换成一个小根堆
* @param data
*/
public MaxHeap(int[] data) {
this.data = data;
buildMaxHeap();
}
/**
* 将数组转换成最大堆
* 数组中,父节点对应的位置是i*2,左子节点对应的是i * 2 + 1, 右子节点对应的是i * 2 + 2
*/
private void buildMaxHeap() {
// 完全二叉树只有数组下标小于或等于 (data.length) / 2 - 1 的元素有孩子结点,遍历这些结点。
// 比如{0, 1, 2, 3, 4},(data.length) / 2 - 1 = 1,其左右子节点是3和4,而2,3,4没有子节点
for (int i = (data.length) / 2 - 1; i >= 0; i--) {
// 对有子结点的元素heapify
heapify(i);
}
}
/**
* 调整节点i及其子节点的位置以符合最大堆的要求,即root是最大值
* 如果是求数组最大的K个数,则使用最小堆,即左右子节点比root大的调换位置即可
* @param i
*/
private void heapify(int i) {
// 获取左右结点的数组下标 (i + 1) << 1 = 2 * i + 2
int left = ((i + 1) << 1) - 1;
int right = (i + 1) << 1;
// 这是一个临时变量,表示 跟结点、左结点、右结点中最大的值的结点的下标
int largest = i;
// 存在左结点,且左结点的值大于根结点的值
if (left < data.length && data[left] > data[largest]) {
largest = left;
}
// 存在右结点,且右结点的值大于以上比较的较大值
if (right < data.length && data[right] > data[largest]) {
largest = right;
}
// 左右结点的值都小于根节点,直接return,不做任何操作
if (i == largest) {
return;
}
// 交换根节点和左右结点中最大的那个值,把根节点的值替换下去
swap(i, largest);
// 由于替换后左右子树会被影响,所以要对受影响的子树再进行heapify
heapify(largest);
}
/**
* 交换元素位置
* @param i
* @param j
*/
private void swap(int i, int j) {
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
/**
* 获取对中的最小的元素,根元素
* @return
*/
public int getRoot() {
return data[0];
}
/**
* 替换根元素,并重新heapify
* @param root
*/
public void setRoot(int root) {
data[0] = root;
heapify(0);
}
}
public static int[] findLeastK(int[] array, int k) {
if (array == null || array.length == 0 || k <= 0) {
return null;
}
int[] result = new int[k];
// 先取出数组的前K个元素来构造最大堆
for (int i = 0; i < k; i++) {
result[i] = array[i];
}
MaxHeap maxHeap = new MaxHeap(result);
// 遍历数组其余的元素并替换最大堆的root
for (int i = k; i < array.length; i++) {
int max = maxHeap.getRoot();
if (array[i] < max) {
maxHeap.setRoot(array[i]);
}
}
return result;
}
public static void main(String[] args) {
int[] array = {4, 5, 1, 6, 2, 7, 3, 8};
int[] result = findLeastK(array, 4);
for (int i : result) {
System.out.print(i + " ");
}
}
输出是 4 3 1 2