目录
题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
测试用例
- 功能测试(输入的数组中有相同的数字;输入的数组没有相同的数字)
- 边界值测试(输入的k等于1或者数组的长度)
- 特殊输入测试(k 小于 1; k 大于数组的长度;指向数组的指针为空指针)
题目考点
-
考察应聘者对时间复杂度的理解。应聘者每想出一种解法,面试官都期待他能分析出这种解法的时间复杂度是多少。
-
如果可以改变数组顺序,那么就是考察Partition函数,考察快排函数的基础。
-
如果不可以改变数组顺序,那么就是考察对堆、红黑树等数据结构的理解。
解题思路
API解题
直接使用sortAPI,见自己解题,有点慢。
时间复杂度O(nlogn)
利用Partition函数解题
不断partition,直到找到下标k-1,然后输出前k个数字。
时间复杂度为O(n);不好的地方在于会改变原数组。
利用堆解题
构建一个k大小的大顶堆,当之后的元素大于堆顶元素,则抛弃堆顶,让该元素入堆
时间复杂度为O(nlogk),空间复杂度O(k),适合用于处理大数据
利用红黑树解题
构建一个k大小的红黑树,之后的原理和堆一样
时间复杂度为O(nlogk),空间复杂度O(k),适合用于处理大数据
我们这里采用基于黑红树实现的数据结构TreeSet实现
自己解题
/**
* 最小的 k 个数
*/
public class Solution {
/**
* 直接用StreamAPI
* @param input
* @param k
* @return
*/
public ArrayList<Integer> getLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> result = new ArrayList<>();
if (input == null || input.length == 0) {
return result;
}
if (k < 0 || k > input.length) {
return result;
}
List<Integer> list = Arrays.stream(input).boxed().sorted().limit(k).collect(Collectors.toList());
return (ArrayList<Integer>) list;
}
}
240ms
参考解题
利用堆解题
import java.util.ArrayList;
public class Solution {
// 利用一个能够装k个元素的容器,如果容器满了,做以下3中事情:
// 1、在k个整数中找最大值M
// 2、逐个读取输入数组a[i],和M作比较,前者小就将M从容器中删除
// 3、可能插入a[i]
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
// 利用(最大)堆
// 优点:适合海量数据,因为不需要一次性全部储存所有数据
ArrayList<Integer> result = new ArrayList<Integer>();
// 异常
if(input == null || input.length <= 0 || input.length < k || k < 1 ){
return result;
}
// 构建容量为k的最大堆
for(int i = ( k >> 1 ); i >= 0; i--){
// 对每个数字从右到左使用sink函数构造子堆,并且下沉排序,
sink(input, i, k-1);
}
// 从第k个元素开始分别与最大堆的最大值作比较,如果比最大值小,则替换并调整堆
// 最终堆里的就是最小的k个数
int tmp;
for(int i = k; i < input.length; i++){
if(input[i] < input[0]){
tmp = input[0]; // input[0]表示最大值
input[0] = input[i];
input[i] = tmp;
sink(input, 0, k-1);
}
}
for(int j = 0; j < k ; ++j){
result.add(input[j]);
}
return result;
}
// ----功能函数sink,堆有序化,下沉实现,小数下沉直到满足堆有序----------------------------------------------------------
public void sink(int[] input, int curNode, int endOfHeap){
int child = 2*curNode;
while(child <= endOfHeap){
// 取较大的子节点
if(child < endOfHeap && input[child]< input[child+1]) child++;
// 满足堆有序,不用下沉
if(input[curNode] >= input[child]) break;
// 不满足,下沉,交换当前节点和其较大的子节点
swap(input, curNode, child);
curNode = child;
child = 2*curNode;
}
}
// -----交换函数--------------------
public void swap(int[] a, int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
15ms,贼快
利用红黑树解题
/**
* 最小的 k 个数
*/
public class Solution2 {
/**
* 利用基于红黑树实现的TreeSet
*
* @param input
* @param k
* @return
*/
public ArrayList<Integer> getLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> list = new ArrayList<Integer>();
// 防止特殊输入
if (input == null || input.length <= 0 || input.length < k || k < 1) {
return list;
}
// 利用TreeSet构建一颗k大小的黑红树
TreeSet<Integer> treeset = new TreeSet<Integer>();
for (int i = 0; i < k; i++) {
treeset.add(input[i]);
}
// 之后的数与红黑树最大值的比较,如果比最大值大,替换数中最大值
for (int i = k; i < input.length; i++) {
// 拿到树中的最大值
int max = treeset.last();
if (input[i] < max) {
treeset.remove(max);
treeset.add(input[i]);
}
}
// 包装成list
Iterator<Integer> it = treeset.iterator();
while (it.hasNext()) {
list.add(it.next());
}
return list;
}
}
26ms,也可以了
补充
如果面试时遇到的面试题有多种解法,并且每种解法都各有优缺点,那么我们要向面试官问清楚题目的要求,输入的特点,从而选择最合适的解法。