我GitHub上的:剑指offer题解
题目:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
思路1:把输入的n个整数进行排序,排序之后取前面的k个数。时间复杂度O(nlogn)
思路2:基于partition函数。如果基于数组的第k个数字来调整,则使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。这样调整之后,位于数组中左边的前k个数字就是我们要求的数字(但这几个数字不一定是排序的),时间复杂度O(n),但是只有当我们可以修改数组时可以用。
要熟悉partition函数的写法。
package basic;
import java.util.ArrayList;
public class Partition {
public static int partition(int[] arr, int start, int end) {
int key = arr[start];
int i = start;
int j = end;
while(i < j) {
// 从右往左找到比key小的数字
while(i < j && arr[j] >= key) {
j--;
}
if (i < j) {
swap(arr, i++, j);
}
while(i < j && arr[i] <= key) {
i++;
}
if (i < j) {
swap(arr, i, j--);
}
}
arr[i] = key;
return i;
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> result = new ArrayList<>();
if (input == null || k <= 0 || k > input.length) {
return result;
}
int start = 0;
int end = input.length - 1;
int index = partition(input, start, end);
while(index != k - 1) {
if (index < k - 1) {
index = partition(input, index + 1, end);
}
else {
index = partition(input, start, index - 1);
}
}
for(int i = 0 ; i <= index; i++) {
result.add(input[i]);
}
return result;
}
public static void main(String[] args) {
int[] arr = {8,1,4,9,11,4,13,55,3};
ArrayList<Integer> result = GetLeastNumbers_Solution(arr, 9);
for (Integer integer : result) {
System.out.print(integer + " ");
}
}
}
思路3:利用堆排序,建立大顶堆,将前K个数插入到堆中,然后从读入下一个元素,与堆顶元素进行比较。如果比堆顶元素大,就抛弃这个数,继续读下一个数,如果较小,就删除堆顶元素,插入这个数。
时间复杂度O(nlogk)
package shuti;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
* @author yajie
*
*/
public class GetLeastNumbers_Solution40 {
ArrayList<Integer> res = new ArrayList<>();
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
// 最小的k个数,适合用堆
// 用大顶堆,保存k个较小的数,最顶端是k个数里面最大的。
// 新增加的要先和堆顶比较,如果比堆顶大,就不用加入,如果比堆顶小,就删除堆顶。
// java中的优先队列PriorityQueue默认是一个小顶堆,然而可以通过传入自定义的Comparator函数来实现大顶堆
ArrayList<Integer> result = new ArrayList<>();
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]) {
// 堆顶大于input[i],删除堆顶的元素
maxHeap.remove();
maxHeap.offer(input[i]);
}
}
while(!maxHeap.isEmpty()) {
result.add(maxHeap.poll());
}
return result;
}
}
思路三的方法有两个明显的优点:
1.没有修改输入的数据
2.适合海量数据的输入。假设题目的要求是从海量数据中找出最小的k个数字,由于内存的大小是有限的,有可能不能把这些海量的数据一次性全部载入内存。这时候,我们可以从辅助存储空间(如硬盘)中每次读入一个数字,根据上述的方法判断需不需要放入容器中。这种思路只要求内存能够容纳k个数就好了。因此它最适合的情形就是n很大,并且k较小的问题。