题目:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
解法一:基于快排。如果基于数组的第K个数字来调整,使得比第K个数字小的都为于左边,比它大的都位于右边,这样调整之后,位于数组中左边的K个数字就是最小的K个数字。该方法优点时间复杂度O(n),但缺点是需要修改输入数组,而且不适用于海量数据。
public class Solution {
//方法一:基于快排的思想
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> result = new ArrayList<>();
if(k>input.length ||k<0){
return result;
}
int high=input.length-1;
int low=0;
int privoIndex=partition(input,low,high);
while(privoIndex!=k-1){
if(privoIndex>k-1){
privoIndex=partition(input,low,privoIndex-1);
}else{
privoIndex=partition(input,privoIndex+1,high);
}
}
for(int i=0;i<k;i++)
result.add(input[i]);
return result;
}
public int partition(int[] a,int low,int high){
int privokey=a[low];
while(low<high){
while(low<high && a[high]>=privokey)
--high;
swap(a,low,high);
while(low<high && a[low]<=privokey)
++low;
swap(a,low,high);
}
return low;
}
public void swap(int[] a,int i,int j){
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
public static void main(String[] args) {
Solution s=new Solution();
int[] a={5,3,7,8,10,9,2};
int k=3;
ArrayList<Integer> result=s.GetLeastNumbers_Solution1(a,k);
for(Integer i:result)
System.out.println(i);
}
}
方法二:基于最大堆排序,构造大小为K的最大堆,从第k+1个数开始与堆顶的数比较,如果小于堆顶,则进入堆把原堆顶拿出来并且调整堆。最后,数组前K个数即为最小的K个数。该方法的优点是不需要修改输入的数组并且适用于海量数据,但时间复杂度为O(n*logk)。
public class Solution {
//方法二:基于最大堆排序,保存前k个数,从第k+1个数开始调整堆
public ArrayList<Integer> GetLeastNumbers_Solution1(int [] input, int k) {
ArrayList<Integer> result=new ArrayList<>();
if(k<=0 || k>input.length) return result;
//初始堆
BuildHeap(input,k);
for(int i=k;i<input.length;i++){
if(input[i]<input[0]){
int temp=input[0];
input[0]=input[i];
input[i]=temp;
AdjustHeap(input,0,k);
}
}
for(int i=0;i<k;i++)
result.add(input[i]);
return result;
}
public void BuildHeap(int[] array,int length){
//(length-1)/2是最后一个有孩子结点的位置
for(int i=(length-1)/2;i>=0;i--){
AdjustHeap(array,i,length);
}
}
//调整array[s],使其成为大顶堆.即将对第s个结点为根的子树筛选
public void AdjustHeap(int[] array,int s,int length){
int temp=array[s];
int child=2*s+1; //左孩子结点位置
while(child<length){
if(child+1<length && array[child]<array[child+1])
++child;
if(array[s]<array[child]){
array[s]=array[child];
array[child]=temp;
s=child; //调整s结点
child=2*s+1; //重新调整新的s结点的左孩子结点
}else{
break;
}
}
}
public static void main(String[] args) {
Solution s=new Solution();
int[] a={5,3,7,8,10,9,2};
int k=3;
ArrayList<Integer> result=s.GetLeastNumbers_Solution1(a,k);
for(Integer i:result)
System.out.println(i);
}
}
方法三:维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
if(k<=0||k>input.length) return new ArrayList<>();
//创建大根堆
PriorityQueue<Integer> maxheap=new PriorityQueue<>(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for(Integer s:input){
maxheap.add(s);
if(maxheap.size()>k){
maxheap.poll();
}
}
ArrayList<Integer> arrayList=new ArrayList<>(maxheap);
return arrayList;
}
}
方法四:构造大小为K的小根堆,将数组中的所有元素加入小根堆中,循环K次输出小根堆堆顶元素,即为当前最小的K个数。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> arrayList=new ArrayList<>();
if(k>input.length||k<=0) return arrayList;
PriorityQueue<Integer> priorityQueue=new PriorityQueue<>(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
for(int i=0;i<input.length;i++){
priorityQueue.add(input[i]);
}
for(int i=0;i<k;i++){
arrayList.add(priorityQueue.poll());
}
return arrayList;
}
}