一、题目
输入n个整数,找出其中最小的k个数。
例子说明:
例如输入4 、5 、1、6、2、7、3 、8 这8 个数字,则最小的4 个数字是1 、2、3 、4
解法一:O(n)时间算法,只有可以修改输入数组时可用。
可以基于Partition函数来解决这个问题。如果基于数组的第k个数字来调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。这样调整之后,位于数组中左边的k个数字就是最小的k 个数字(这k 个数字不一定是排序的〉。
/**
* 题目: 输入n个整数,找出其中最小的k个数。
* 【第一种解法】
* @param input 输入数组
* @param output 输出数组
*/
public static void getLeastNumbers(int[] input,int k) {
int[] output=new int[k];
while(input==null || k<=0 || k>input.length) {
return output;
}
int start = 0;
int end = input.length - 1;
int index = partition(input, start, end);
while (index != k-1) {
if (index < k-1) {
start = index + 1; //前面筛选的不够,后面的接着筛选
} else {
end = index - 1;
}
index = partition(input, start, end);
}
System.arraycopy(input, 0, output, 0, output.length);
}
/**
* 分区算法
*
* @param input 输入数组
* @param start 开始下标
* @param end 结束下标
* @return 分区位置
*/
private static int partition(int[] input, int start, int end) {
int tmp = input[start];
while (start < end) {
while (start < end && input[end] >= tmp) {
end--;
}
input[start] = input[end];
while (start < end && input[start] <= tmp) {
start++;
}
input[end] = input[start];
}
input[start] = tmp;
return start;
}
解法二: O(nlogk)的算法,精剧适合处理海量数据。
思路三
可以先创建一个大小为k的数据容器来存储最小的k个数字,从输入的n个整数中一个一个读入放入该容器中,如果容器中的数字少于k个,按题目要求直接返回空;
如果容器中已有k个数字,而数组中还有值未加入,此时就不能直接插入了,而需要替换容器中的值。按以下步骤进行插入:
先找到容器中的最大值;
将待查入值和最大值比较,如果待查入值大于容器中的最大值,则直接舍弃这个待查入值即可;如果待查入值小于容器中的最小值,则用这个待查入值替换掉容器中的最大值;
重复上述步骤,容器中最后就是整个数组的最小k个数字。
对于这个容器的实现,我们可以使用最大堆的数据结构,最大堆中,根节点的值大于它的子树中的任意节点值,
我们可以使用一个大顶堆在O(logk)时间内实现这三步操作
当涉及到频繁查找和替换最大最小值时,二叉树是非常合适的数据结构,要能想到堆和二叉树。
方式一:PriorityQueue
PriorityQueue是个基于优先级堆的极大优先级队列。
此队列按照在构造时所指定的顺序对元素排序,既可以根据元素的自然顺序来指定排序(参阅 Comparable),
也可以根据 Comparator 来指定,这取决于使用哪种构造方法。优先级队列不允许 null 元素。
原文链接: https://www.cnblogs.com/zzmher/p/7730700.html
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> result = new ArrayList<Integer>();
int length = input.length;
if (k > length || k == 0) {
return result;
}
PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
for (int i = 0; i < length; i++) {
if (maxHeap.size() != k) {
maxHeap.offer(input[i]);
} else if (maxHeap.peek() > input[i]) {
Integer temp = maxHeap.poll();
temp = null;
maxHeap.offer(input[i]);
}
}
for (Integer integer : maxHeap) {
result.add(integer);
}
return result;
}
}
方式二:
Java中的TreeSet类实现了红黑树的功能,它底层是通过TreeMap实现的,TreeSet中的数据会按照插入数据自动升序排列(按自然顺序)。因此我们直接将数据依次放入到TreeSet中,数组就会自动排序。
缺点就是:
TreeSet不允许重复数据,因为TreeSet的底层是TreeMap实现,是将TreeSet添加的内容作为TreeMap的key值来存储,也就不能存在重复数据。由于这种限制,这就对我们的输入数组有要求,但我们可以通过自己实现最大堆或优化TreeSet来实现兼容存在重复数字的情况。
原文链接:https://blog.csdn.net/shakespeare001/article/details/51280814
public static ArrayList<Integer> GetLeastNumbers_Solution3(int [] input, int k) {
if(input == null)
return null;
ArrayList<Integer> list = new ArrayList<Integer>(k);
if(k > input.length)
return list;
TreeSet<Integer> tree = new TreeSet<Integer>();
for(int i = 0 ; i < input.length; i++){
tree.add(input[i]);
}
int i = 0;
for(Integer elem : tree){
if(i >= k)
break;
list.add(elem);
i++;
}
return list;
}
方式三:
原文链接:
https://www.cnblogs.com/yongh/p/9944155.html
/**
* 最大堆
*
* @param input
* @param k
* @return
*/
public static ArrayList<Integer> GetLeastNumbers_Solution2(int[] input, int k) {
ArrayList<Integer> leastNumbers = new ArrayList<Integer>();
while (input == null || k <= 0 || k > input.length)
return leastNumbers;
int[] numbers = new int[k]; //用于放最小的k个数
for (int i = 0; i < k; i++)
numbers[i] = input[i];//先放入前k个数
for (int i = k / 2 - 1; i >= 0; i--) {
adjustHeap(numbers, i, k - 1);//将数组构造成最大堆形式
}
for (int i = k; i < input.length; i++) {
if (input[i] < numbers[0]) { //存在更小的数字时,将第一个替换,然后再排序
numbers[0] = input[i];
adjustHeap(numbers, 0, k - 1);//
}
}
for (int n : numbers)
leastNumbers.add(n);
return leastNumbers;
}
private static void adjustHeap(int[] arr, int start, int end) {//最大堆
int temp = arr[start];//记录根节点
int child = start * 2 + 1; //从0号排序,左孩子就是2*i+1
while (child <= end) {
if (child + 1 <= end && arr[child + 1] > arr[child])//右孩子>左孩子,选择右孩子
child++;
if (arr[child] < temp)//如果左子孩子<根节点 结束,本身就是最大堆
break;
arr[start] = arr[child];//
start = child;//根节点
child = child * 2 + 1;//左孩子
}
arr[start] = temp;
}