題目來源:力扣
題目描述:
輸入整數數組 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;
}
}