返回最小的前K个数
题目:返回最小的前K个数
注:一千个人心理有一千个哈姆雷特。哪怕思路一致,1000个人可能写出的代码都会有1000种形式,在此我只是分享下思路,至于代码实现,各位看客参考即可,如果有更好的思路,欢迎在评论区
问题描述:
给定一个数组,找出其中最小的K个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。如果K>数组的长度,那么返回一个空的数组
输入:
[4,5,1,6,2,7,3,8],4
返回值:
[1,2,3,4]
问题分析:
首先看到这个题目,我们应该可以想到,如果想要返回最小的前k个数,之前有类似的题目是返回最小的第K个数,看似和这个题相似,但是其中还有点区别。这道题我们需要拿到k前面的k-1个数,也就是排完序之后的arr[0]---->arr[k-1]。处理这个问题的时候,有两种思维方式,第一种是将数组全部排完序,然后一次性得到结果;第二种是每次拿一个数,并且保证每次拿的数是剩余元素中最小的。然后拿k次,最后返回结果。两种方式没有好坏之分,一次性拿需要全局排序,一劳永逸;分批次拿每次都会浪费时间,所以具体看实际情况应用即可。
1)克隆数组比较法:
最直接可以想到的方法就是,题目所给数组假设为a,我们新建一个数组b,然后遍历a的元素,在插入b的时候我们加入比较规则,这样当a的元素全部插入到b的时候,那么b就是有序数组。我们直接返回前k个元素即可
2)优先队列取头法:
第一个方法我们需要手动实现比较规则,既然如此,我们可以使用java自带的优先队列(优先队列保证队列的第一个元素是极大/极小值,不保证全局有序)。我们可以把数组a的元素全部插入队列,在插入的同时,队列会自动调整队列头元素的值(类似于堆中的调整堆过程)。
等全部插入之后,依次从队列中取出元素(取出后也会有一个调整的过程来保证队列头为极大/极小)。
取k次,然后将取到的数据返回即可
3)快排区间定位法:
众所周知,快排是一种将散列的数组高效规整的算法之一。数组越乱,快排越有奇效。
此处,我们使用快排是因为快排采用分而治之的思想,每次都是类似一半一半处理,在此题中,我们恰好也不需要全局有序,只需要前k个数有序即可。所以这里我们可以使用快排。
当快排的哨兵位置就是k的时候,我们直接返回0–k的元素即可。(“哨兵”是快排中的“中间值”,max[哨兵左元素们]<=value[哨兵]<=min[哨兵右元素们])
代码实现:
1)克隆数组比较法
该算法实现较为简易,此处暂先略过
2)优先队列取头法:
/**
* 优先队列法
* 描述:优先队列允许传入元素按照定义的大小顺序存储
* 思路:遍历给定数组,将元素依次插入到队列,之后从队列中依次取出前k个数
*
* 总结:
* 1)PriorityQueue类似于大小顶堆,只能保证第一个元素是最大或者最小,不保证除此之外其他元素的大小关系。所以在debug期间可能会看到queue的内部顺序是乱的,但是第一个元素始终是极大或者极小。
* 2)当调用了.poll()之后,内部会重新排序,同时也会保证第一个元素是有序的。
* 3)初始化的时候可以自定义排序方式,jkd 1.8支持lambda表达式作为类传参。
*/
public ArrayList<Integer> Solution_01(int[] input,int k){
if(k>input.length){
return new ArrayList<>();
}
PriorityQueue<Integer> queue = new PriorityQueue<Integer>((x,y)->x-y);
for(int i:input){
queue.add(i);
}
ArrayList<Integer> res = new ArrayList<>();
for(int i=0;i<k;i++){
res.add(queue.poll());
}
return res;
}
3)快排区间定位法:
/**
* 快排找数法
* 描述:快排是一种将一组杂乱无序元素快速整理的算法。"哨兵"分割的元素,max(左边)<= "哨兵" <= min(右边)
* 思路:按照快排的方式,如果"哨兵" = k,那么直接返回哨兵左边的k个数据即可
*
* @param input,K
*/
public ArrayList<Integer> Solution_02(int[] input,int k){
QSort(input,0,input.length-1,k);
ArrayList<Integer> res = new ArrayList<>();
for(int i=0;i<k;i++){
res.add(input[i]);
}
return res;
}
public void QSort(int[] arr,int low,int high,int k){
if(low>=high) return;
int left = low;
int right = high;
int refer = arr[low];//基准
while(low<high){
while(refer<=arr[high] && low<high){
high--;
}
while(refer>=arr[low] && low<high){
low++;
}
//此刻的arr[high]<refer arr[low]>refer 交换顺序
if (low<high){
int temp = arr[high];
arr[high] = arr[low];
arr[low] = temp;
}
}
//最终交换基准和low/high的值
arr[left] = arr[low];
arr[low] = refer;
if(k == low){
return ;
}
if(low > k){
QSort(arr,left,low-1,k);
}else{
QSort(arr,low+1,right,k);
}
}