面试题 17.14. 最小K个数
设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。
最常规也是最容易想到的思路就是,先对数组进行排序,再遍历输出前四个元素即可。
public int[] smallestK(int[] arr, int k) {
Arrays.sort(arr);
int[] ret = new int[k];
for (int i = 0; i < k; i++) {
ret[i] = arr[i];
}
return ret;
}
但是这样的时间复杂度实在是太高了O(nlogn),一般在面试的过程中都会要求时间复杂度。一般这种TOPK问题都可以使用优先级队列解决。
这里有一个小技巧,经验之谈。取大用小,取小用大。若需要取出前k个最大元素构造最小堆,
若需要服出前k个最小元素构造最大推。
思路:
1.首先取出前四个最小元素,我们需要构建一个大小为4个最大堆
2.若此时队列当中元素个数小于k值,则直接添加
3.若此时队列当中元素个数等于K值,
a.新扫描的元素值val大于等于堆顶元素,则该值一定大于堆中所有元素,这个val一定不是所求元素,直接跳过。
b.新扫描val小于堆顶元素,就把堆顶元素出队,将新元素val添加到队列中。
c.重复上述过程,直到整个集合被我们扫描完毕,队列中恰好就保存了前k个最小值。
4.随着堆顶元素不断交换,会把堆顶元素不断变小,最终队列扫描结束就存放了最小的k个数。
此时的时间复杂度为O(nlogk) 。n是遍历数组的时间复杂度,logk是堆中有K个元素。当k<<n是时间复杂度就很明显啦。
动态图解:
代码实现:
因为JDK内置的优先级队列是一个最小堆,许我们这里使用的是最大堆,所以要借用Comparator接口改造一下。
class IntegerRevese implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
public int[] smallestK(int[] arr, int k) {
int[] ret = new int[k];
//边界判空
if (arr.length == 0 || k == 0) {
return ret;
}
//因为JDK内置的优先级队列是一个最小堆,许我们这里使用的是最大堆,所以要借用Comparator接口改造一下
Queue<Integer> queue = new PriorityQueue<>(new IntegerRevese());
for (int i = 0; i < arr.length; i++) {
//若此时队列当中元素个数小于k值,则直接添加
if (queue.size() < k) {
queue.offer(arr[i]);
} else {
//若此时队列当中元素个数大于K值
int max = queue.peek();
//新扫描的元素值val小于堆顶元素。
if (arr[i] < max) {
queue.poll();
queue.offer(arr[i]);
}
}
}
//当前队列中保存前k个最小元素。
int i = 0;
while (!queue.isEmpty()) {
ret[i++] = queue.poll();
}
return ret;
}