题目
输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
思路
思路一
先对n个数排序,再找到前k个数。在排序算法中,最快的排序需要O(nlogn)的时间复杂度。
但这里并不需要真的对所有数据进行排序,可以利用快排中的partition方法。
- 1.即利用partition方法,将比某个数字小的都放在数组左边,大的都放在数组右边。
- 再判断这个数字所在位置index,或index<k,则继续找左数组;若index>k,则继续找右数组。
- 直到index=k。
复杂度分析,partition的复杂度是O(n)。则第一次n,第二次n/2… 最后一次1
则n+n/2+n/4+…1 = 2n-1,因此时间复杂度是O(n)
思路二
利用最大堆。即遍历n个数,用最大堆存放最小的k个数,当遍历的数字比最大堆中最大的数字还小,则替换堆顶,并调整堆。
Java实现
实现一
package offer.SortAndSearch;
import java.util.ArrayList;
/**
* 面试题 40: 最小的k个数
* 方法一:利用快排的partition方法,使得前k小的数字都在数组的左边,时间复杂度O(n)
* 方法二:利用最小堆,存放前k小的数字。时间复杂度O(klogn)
*/
public class GetLeastNumbers {
/**
* 方法一
* @param input
* @param k
* @return
*/
public ArrayList<Integer> GetLeastNumbers_Solution1(int [] input, int k) {
ArrayList<Integer> leastK = new ArrayList<>();
if (input.length == 0 || k > input.length || k <= 0) {
return leastK;
}
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, start + index + 1, end);
} else {
index = partition(input, start, index - 1);
}
}
for (int i = 0; i < k; i++) {
leastK.add(input[i]);
}
return leastK;
}
public int partition(int[] arr, int s, int e) {
int pivot = arr[e];
int i = s;
for (int j=s; j < e; j++) {
if (arr[i] < pivot) {
swap(arr, i, j);
i++;
}
}
swap(arr, i, e);
return i;
}
public int partition2(int[] array, int start, int end) {
int key = array[end];
while (start < end) {
while (array[start] <= key && start < end) { // 这里的等号必须要有,因为等于key的不需要交换
start++;
}
swap(array, start, end);
while (array[end] >= key && start < end) {
end--;
}
swap(array, start, end);
}
return start;
}
public void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public void printArrayList(ArrayList<Integer> array) {
for (int i = 0; i < array.size(); i++) {
System.out.println(array.get(i));
}
}
public static void main(String[] args) {
GetLeastNumbers getLeastNumbers = new GetLeastNumbers();
int[] input = {4,5,1,6,2,7,3,8};
int k = 4;
ArrayList<Integer> re = getLeastNumbers.GetLeastNumbers_Solution2(input, k);
getLeastNumbers.printArrayList(re);
}
}
实现二
···
import java.util.ArrayList;
/**
-
面试题 40: 最小的k个数
-
方法一:利用快排的partition方法,使得前k小的数字都在数组的左边,时间复杂度O(n)
-
方法二:利用最小堆,存放前k小的数字。时间复杂度O(klogn)
/
public class GetLeastNumbers {
/*-
方法二
-
利用最小堆,通过插入数据,堆化实现最小k的数据的保存
-
堆化:从下往上和从上往下。堆化-顺着节点所在路径,往上或往下,比较,交换。
-
@param input
-
@param k
-
@return
*/
public ArrayList GetLeastNumbers_Solution2(int [] input, int k) {
ArrayList leastK = new ArrayList<>();
if (input.length == 0 || k > input.length || k <= 0) {
return leastK;
}
int [] maxStack = new int[k];
int i = 0;
for (; i < k; i++) {
maxStack[i] = input[i];
}
heapify(maxStack, 0);
for (;i < input.length; i++) {
if (input[i] < maxStack[0]) {
maxStack[0] = input[i];
heapify(maxStack, 0);
}
}for (i = 0; i < k; i++) {
leastK.add(maxStack[i]);
}
return leastK;
}
public void heapify(int[] arr, int i) {
int len = arr.length;
while (true) {
int maxPos = i;
if (i2 <= len - 1 && arr[i2] > arr[i]) {
maxPos = i2;
}
if (i2+1 <= len - 1 && arr[i2+1] > arr[i]) {
maxPos = i2+1;
}
if (maxPos == i) break;
swap(arr, i, maxPos);
i = maxPos;
}
}public static void main(String[] args) {
GetLeastNumbers getLeastNumbers = new GetLeastNumbers();
int[] input = {4,5,1,6,2,7,3,8};
int k = 4;
ArrayList re = getLeastNumbers.GetLeastNumbers_Solution2(input, k);
getLeastNumbers.printArrayList(re);
}
} -
···
收获
1.算法比较:
思路一 | 思路二 | |
---|---|---|
时间复杂度 | O(n) | O(nlogk) |
是否需要修改输入数组 | 是 | 否 |
是否适合海量数据 | 否 | 是 |
2.顺便复习快排的partition方法和堆的堆化方法。