题目来源:力扣
题目描述:
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
审题:
对于TopM问题,当数据量较小时,可以直接对数据进行排序,然后选择前k个数值即可.而对于海量大数据,例如十亿,百亿的数据量,如果要找出前10个数值,则使用排序算法是不现实的.因为海量的数据时可能无法一次性装进内存,更别提后续的排序过程了.
TopM问题的经典处理方法是基于优先队列进行解决.如果要出道最小的k个数,我们可以向向最大优先队列依次插入数值,如果插入后队列中元素个数大于k,则弹出最大的元素.最终当插入完所有元素后,队列中剩余的k个元素即是最小的k个值. 对上述步骤可以略做调整,如果当前队列中元素个数等于:如果当前元素小于队列中最大元素,则插入该元素,然后删除最大元素,如果当前元素大于最大元素,则不插入该元素.
优先队列通常使用堆结构实现,在该篇文章中,我们将使用数组构建最大堆,以实现最大优先队列.
java算法:
class Solution {
//基于最大堆实现优先队列
//为了选择最小的k个数,我们需要保存所有k+1个值
//为了便于计算父节点下标与子节点下标,我们从下标1开始存储元素
private int[] pq;
int pqSize;
//插入最底部,然后上浮
private void insert(int x){
pq[++pqSize] = x;
int i = pqSize;
while(i > 1){
if(pq[i] > pq[i/2]){
int tmp = pq[i];
pq[i] = pq[i/2];
pq[i/2] = tmp;
i = i/2;
}
else
break;
}
}
//下沉
private int delMax(){
int max = pq[1];
pq[1] = pq[pqSize--];
if(pqSize > 0){
int i = 1;
while(i <= pqSize / 2){
//选择最大的子节点
if(pq[2*i] < pq[2*i+1]){
if(pq[i] < pq[2*i+1]){
int tmp = pq[i];
pq[i] = pq[2*i+1];
pq[2*i+1] = tmp;
i = 2*i+1;
}
else
break;
}
else if(pq[i] < pq[2*i]){
int tmp = pq[i];
pq[i] = pq[2*i];
pq[2*i] = tmp;
i = 2*i;
}
else
break;
}
}
return max;
}
public int[] getLeastNumbers(int[] arr, int k) {
pq = new int[k+2];
for(int val: arr){
if(pqSize < k){
insert(val);
}
else{
if(val < pq[1]){
insert(val);
delMax();
}
}
}
int[] minK = new int[k];
System.arraycopy(pq, 1, minK, 0, k);
return minK;
}
}