本文參考自《劍指offer》一書,代碼采用Java語言。
題目
輸入n個整數,找出其中最小的k個數。例如輸入4、5、1、6、2、7、3、8這8個數字,則最小的4個數字是1、2、3、4。
思路
思路一:同劍指offer(39) 數組中出現次數超過一半的數字中使用partition()方法,基於數組的第k個數字調整,使得更小的k個數字都在數組左邊即可。
思路二:依次遍歷n個整數,用一個容器存放最小的k個數字,每遇到比容器中最大的數字還小的數字時,將最大值替換為該數字。容器可以使用最大堆或者紅黑樹來實現。本文根據堆排序的原理來實現。
測試算例
1.功能測試(數組中存在/不存在重復數字)
2.邊界值測試(k=1或者等於數組長度)
2.特殊測試(null、k<1、k大於數組長度)
Java代碼
//題目:輸入n個整數,找出其中最小的k個數。例如輸入4、5、1、6、2、7、3、8
//這8個數字,則最小的4個數字是1、2、3、4。
public class KLeastNumbers {
/*
* 方法一:采用partition()
*/
public ArrayList<Integer> GetLeastNumbers_Solution1(int [] input, int k) {
ArrayList<Integer> leastNumbers = new ArrayList<Integer>();
while(input==null || k<=0 || k>input.length)
return leastNumbers;
int start=0;
int end=input.length-1;
int index=partition(input,start,end);
while(index!=k-1){
if(index<k-1){
start=index+1;
index=partition(input,start,end);
}else{
end=index-1;
index=partition(input,start,end);
}
}
for(int i=0;i<k;i++){
leastNumbers.add(input[i]);
}
return leastNumbers;
}
private int partition(int[] arr, int start,int end){
int pivotKey=arr[start];
while(start<end){
while(start<end && arr[end]>=pivotKey)
end--;
swap(arr,start,end);
while(start<end && arr[start]<=pivotKey)
start++;
swap(arr,start,end);
}
return start;
}
private void swap(int[] arr, int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
/*
* 方法二:基於堆的容器
*/
public ArrayList<Integer> GetLeastNumbers_Solution2(int [] input, int k) {
ArrayList<Integer> leastNumbers = new ArrayList<Integer>();
while(input==null || k<=0 || k>input.length)
return leastNumbers;
int[] numbers=new int[k]; //用於放最小的k個數
for(int i=0;i<k;i++)
numbers[i]=input[i];//先放入前k個數
for(int i=k/2-1;i>=0;i--){
adjustHeap(numbers,i,k-1);//將數組構造成最大堆形式
}
for(int i=k;i<input.length;i++){
if(input[i]<numbers[0]){ //存在更小的數字時
numbers[0]=input[i];
adjustHeap(numbers,0,k-1);//重新調整最大堆
}
}
for(int n:numbers)
leastNumbers.add(n);
return leastNumbers;
}
//最大堆的調整方法,忘記時可以復習一下堆排序。
private void adjustHeap(int[] arr,int start,int end){
int temp=arr[start];
int child=start*2+1;
while(child<=end){
if(child+1<=end && arr[child+1]>arr[child])
child++;
if(arr[child]<temp)
break;
arr[start]=arr[child];
start=child;
child=child*2+1;
}
arr[start]=temp;
}
}
大頂堆可以用PriorityQueue實現,所以方法二可利用API實現如下:
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
if(input==null || input.length<k || k<=0)
return list;
PriorityQueue<Integer> heap = new PriorityQueue<>(k,(i1, i2)->(i2-i1));
for(int i=0; i<input.length; i++){
if(heap.size()<k){
heap.offer(input[i]);
}else if(heap.peek()>input[i]){
heap.poll();
heap.offer(input[i]);
}
}
for(int i: heap)
list.add(i);
return list;
}
收獲
1.k小於等於0的情況別忘記了
2.方法二,只需要在原始數組中進行讀入操作,而所有的寫操作和判斷都是在容器中進行的,不用反復讀取原始數組,思想非常好。
3.記得要弄清楚是否可以改變原始輸入的數組。
4.partition函數:即是快速排序的基礎,也可以用來查找n個數中第k大的數字。
5.當涉及到頻繁查找和替換最大最小值時,二叉樹是非常合適的數據結構,要能想到堆和二叉樹。
6. PriorityQueue重點:
* 大頂堆與小頂堆的構建: new Comporator()
* 如何插值: heap.offer(e)
* 如何獲取堆頂的值: heap.peek(), heap.poll()
