一、题目描述
原题地址
二、整体思路
(1)、快排
数组中最小的k个数就是说把数组升序排列,求[0,k-1]区间上的数。
快排可以得到一个元素在升序排序的数组中的正确位置。在这个位置的左边区间[l,l2-1]上的元素都比它小,在这个位置的右边区间[r2,r]上的元素都比它大。
那么当k<=l2-1时,说明还需要对左边区间[l,l2-1]进行快排,当k>=r2时,说明还需要对右边区间[r2,r]进行快排,当k在[l2,r2-1]时,说明arr[k]已经被放置至数组升序排序的正确位置,那么[0,k-1]上的元素就是题目中要求的k个数。
(2)、大根堆
把数组[0,k-1]区间上的数化为大根堆。然后遍历剩下数组的部分。若遍历到的元素小于堆顶,则替换堆顶。重复此步骤最终可以得到数组中最小的K个数组成的小根堆,堆顶为数组所有元素中第K小的元素。
三、代码
//快排
class Solution {
public int[] smallestK(int[] arr, int k) {
if(arr.length==0 || k==0) return new int[0];
int[] arr2=new int[k];
Random random=new Random();
quicksort(arr,0,arr.length-1,k,random);
for(int i2=0;i2<=k-1;i2++){
arr2[i2]=arr[i2];
}
return arr2;
}
private void quicksort(int[] arr,int l,int r,int k,Random random){
if(l>=r) return;
//arr[l]与数组中元素随机交换,避免快速排序退化导致性能下降
int p=random.nextInt(r-l+1)+l;
int temp=arr[l];
arr[l]=arr[p];
arr[p]=temp;
int i=l+1,l2=l,r2=r+1;
while(i<r2){
if(arr[i]<arr[l]){
int temp2=arr[++l2];
arr[l2]=arr[i];
arr[i]=temp2;
i++;
}else if(arr[i]>arr[l]){
int temp2=arr[--r2];
arr[r2]=arr[i];
arr[i]=temp2;
}else{
i++;
}
}
int temp3=arr[l2];
arr[l2]=arr[l];
arr[l]=temp3;
if(k<=l2-1){
quicksort(arr,l,l2-1,k,random);
}else if(k>=r2){
quicksort(arr,r2,r,k,random);
}else{
return;
}
}
}
//大根堆
class Solution {
public int[] smallestK(int[] arr, int k) {
//排除特殊情况
if(k==0 || arr.length==0) return new int[0];
if(k==1){
int ret=Integer.MAX_VALUE;
for(int i=0;i<arr.length;i++){
ret=Math.min(ret,arr[i]);
}
int[] ret2=new int[1];
ret2[0]=ret;
return ret2;
}
int[] pq=new int[k];
for(int i=0;i<k;i++){
pq[i]=arr[i];
}
for(int i=(k-1)/2;i>=0;i--){
siftDown(pq,i,k);
}
for(int i=k;i<arr.length;i++){
if(arr[i]<pq[0]){
int temp=arr[i];
arr[i]=pq[0];
pq[0]=temp;
siftDown(pq,0,k);
}
}
return pq;
}
public void siftDown(int[] pq,int index,int k){
while((index*2+1)<k){
int j=index*2+1;
if(j+1<k && pq[j+1]>pq[j]) j++;
if(pq[index]<pq[j]){
int temp2=pq[index];
pq[index]=pq[j];
pq[j]=temp2;
index=j;
}else break;
}
}
}