衆所周知,《劍指offer》是一本「好書」。java
爲何這麼說?git
由於在面試老鳥眼裏,它裏面羅列的算法題在面試中出現的頻率是很是很是高的。github
有多高,以我目前很少的面試來看,在全部遇到的面試算法題中,出現原題的機率大概能有6成,若是把基於原題的變種題目算上,那麼這個出現機率能到達9成,10題中9題見過。面試
若是你是個算法菜雞(和我同樣),那麼最推薦的是先把劍指offer的題目搞明白。算法
至於爲何給「好書」這兩個字打引號,由於這本書成了面試官的必備,若是考生不會這本書上的題目,就極可能獲得面試官負面的評價。這本書快要成爲評判學生算法能力的惟一標準,這使得考前突擊變成了一個慣例,反而讓投機倒把成了必要,並不必定能真正的考察考生的算法能力。後端
對於劍指offer題解這個系列,個人寫文章思路是,對於看了文章的讀者,可以:數組
輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。安全
分類:優化時間和空間效率bash
直接對數組排序,排序後前k個數就是答案,排序通常較快的是O(nlogn),顯然這並非時間複雜度最優解。微信
該方法須要改變原數組。
還記得上一題:數組中超過一半的數字麼?這一題的思路和上題相似,僅僅是換成了
這種算法是受快速排序算法的啓發。
在隨機快速排序算法中,咱們先數組中隨機選擇一個數字,而後調整數組中數字的順序,使得比選中的數字小的數字都排在它的左邊,比選中的數字大的數字都排在它的右邊。若是這個選中的數字的下標恰好是k,咱們就獲得了k個小的數字,這些數字在k的左邊,而且沒有通過排序,可是都比k小。
若是它的下標大於k,咱們能夠接着在它的左邊部分的數組中查找。
若是它的下標小於k,那麼中位數應該位於它的右邊,咱們能夠接着在它的右邊部分的數組中查找。
這是一個典型的遞歸過程
詳細細節見代碼註釋。
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
// 因爲本題須要返回ArrayList<Integer>,因此新建之
ArrayList<Integer> list = new ArrayList<>();
// 若輸入數組長度小於k。直接返回數空的ArrayList
if(input.length < k){
return list;
}
findKMin(input,0,input.length-1,k);
for(int i = 0; i < k; i++){
list.add(input[i]);
}
return list;
}
private void findKMin(int[] a, int start, int end, int k){
if(start < end){
int pos = partition(a, start, end);
if(pos == k-1){
return ;
}else if(pos < k-1){
findKMin(a,pos+1,end,k);
}else{
findKMin(a,start,pos-1,k);
}
}
}
// 快排中的每次排序實現(挖坑填數法),返回的是交換後start位置(快排一次後的中軸點,中軸點左邊全是小於它的,右邊都是大於它的)
public int partition(int[] a, int start, int end){
int pivot = a[start];
while(start < end){
while(start < end && a[end] >= pivot){end--;};
a[start] = a[end];
while(start < end && a[start] <= pivot){start++;};
a[end] = a[start];
}
a[start] = pivot;
return start;
}
}
複製代碼
該方法不改變原數組,但時間複雜度比O(n)略微複雜了些。
構造一個最大堆,最大堆的性質就是堆頂是全部堆中數字的最大值,那麼放入k個數字,隨後將數字中k個數字以後的數字依次和堆中的最大數字比較(也就是和堆頂數字比較),若是小於他,就把堆頂數字彈出,放入小的數字,這樣遍歷一邊數組後,獲得一個k個數字的最大堆,這個最大堆裏存的是最小的k個數。
最大堆的性質由Java中的優先隊列,經過天然數的逆序順序進行維護,也就是下面這句構造:
Queue<Integer> queue = new PriorityQueue<>(k, Collections.reverseOrder());
複製代碼
有的小夥伴會問,爲啥最大堆是最小的k個數?
答:說明你對堆還不夠了解,惡補一下堆的性質吧~
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution_2(int [] input, int k) {
// 因爲本題須要返回ArrayList<Integer>,因此新建之
ArrayList<Integer> res = new ArrayList<>();
// 幾種特殊狀況
if (k > input.length|| k == 0) {
return res;
}
// 構造優先隊列,排序方法是天然數順序的逆序,因此是個最大堆,這樣這個堆的堆頂就是全部數中的最大數
Queue<Integer> queue = new PriorityQueue<>(k, Collections.reverseOrder());
for (int i = 0; i < input.length; i++) {
// 最大堆內數字個數少於k,一直添加到k個
if (queue.size() < k) {
queue.add(input[i]);
}
else {
// 若堆內最大的數字大於數組中的數字,則將數字出堆,並放入這個小的數
if (input[i] < queue.peek()) {
queue.remove();
queue.add(input[i]);
}
}
}
// 結束上面循環後,堆內就是最小的k個數
while (!queue.isEmpty()) {
res.add(queue.remove());
}
return res;
}
public static void main(String[] args) {
int[] a = {4,5,1,6,2,7,3,8};
Solution_40 solution_40 = new Solution_40();
System.out.println(solution_40.GetLeastNumbers_Solution(a,4));
}
}
複製代碼
書中提到,第二種堆的方法適合海量數據求k個最小。由於k個數的堆,空間是固定的,當數組超級大,那麼全存入內存都變得不可行的時候,就須要從外存中慢慢讀取數字,而後和這個堆進行比較。
而方法一就必須吧整個數組放入內存中,才能運行,因此不適合海量數據。
我目前是一名後端開發工程師。技術領域主要關注後端開發,數據爬蟲,數據安全,5G,物聯網等方向。
微信:yangzd1102
Github:@qqxx6661
我的博客:
若是文章對你有幫助,不妨收藏起來並轉發給您的朋友們~