劍指Offer_#40_最小的k個數
阿新 • • 發佈:2020-07-14
劍指Offer_#40_最小的k個數
劍指offerContents
題目
輸入整數陣列 arr ,找出其中最小的 k 個數。例如,輸入4、5、1、6、2、7、3、8這8個數字,則最小的4個數字是1、2、3、4。
示例 1:
輸入:arr = [3,2,1], k = 2
輸出:[1,2] 或者 [2,1]
示例 2:
輸入:arr = [0,1,2,1], k = 1
輸出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i]<= 10000
思路分析
方法1:利用快排中的切分partition()函式
快排當中的partition
函式,可以基於陣列中某個元素v
作為標準,將比v
小的元素放到v
的前面,將比v
大的元素放到v
的後面。我們可以不斷呼叫這個函式,縮小切分範圍,直到v
元素的索引剛好是k-1
。此時陣列的前k項就是結果。
與快排不同的是,快排的出口條件是hi = lo + 1
,即需要不斷縮小範圍,直到指標相鄰,最終整個陣列都是有序的。
在本題中,返回的陣列不需要是有序的,所以跟快排有所不同。
關於快排及partition
方法2:大根堆輔助(可用Java中的PriorityQueue實現)
用一個數據容器來儲存最小的k個數字,最合適的是大根堆,每次可以自動彈出最大值。Java中的PriorityQueue可以實現。
在堆的元素個數小於k時,按順序將數組裡數字壓入。
堆的元素超過k時,如果數組裡的數字小於堆裡最大值,替換掉堆裡的最大值。
解答
解法1:利用快排中的切分partition()函式
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (arr.length == 0 || k == 0) return new int[0];
int start = 0;
int end = arr.length - 1;
int index = partition(arr, start, end);
while(index != k - 1){
if(index > k - 1){
end = index - 1;
index = partition(arr, start, end);
}
else{
start = index + 1;
index = partition(arr, start, end);
}
}
//返回nums陣列的前k個元素
return Arrays.copyOf(arr, k);
}
int partition(int[] nums,int lo,int hi){
int v = nums[lo];
//左右掃描指標
//i不需要初始化為lo-1,因為第一個nums[lo]是k,不需要掃描這個元素
int i = lo,j = hi + 1;
while(true){
//錯誤寫法:當k == nums.length時,會導致陣列越界
//while(nums[++i] < v) if(i == hi) break;
//while(nums[--j] > v) if(j == lo) break;
//i從lo + 1開始向後掃描,直到遇到 大於等於v的數字,指標i停留在此
while(++i <= hi && nums[i] < v);
//j從hi開始向前掃描,直到遇到 小於等於v的數字,指標j停留在此
while(--j >= lo && nums[j] > v);
//i > j的情況,已經切分好左右子陣列,不可以再交換了
//i = j的情況,無需交換
if(i >= j) break;
exch(nums, i, j);
}
exch(nums, lo, j);//將v放入正確的位置
return j;
}
//陣列nums作為形參,屬於引用傳遞,所以函式執行後可以改變實參
void exch(int[] nums, int idxA, int idxB){
int tmp = nums[idxA];
nums[idxA] = nums[idxB];
nums[idxB] = tmp;
}
}
複雜度分析
時間複雜度:,因為每次遍歷的範圍可以看作上次的一半,
空間複雜度:不需要藉助額外變數,但是改變了陣列
解法2:大根堆輔助(可用Java中的PriorityQueue實現)
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0 || arr.length == 0) return new int[0];
//使用lambda表示式過載了PriorityQueue當中的compare方法
Queue<Integer> pq = new PriorityQueue<>((v1,v2) -> v2-v1);
//因為這裡不修改陣列元素,所以可以採用foreach迴圈
for(int num:arr){
if(pq.size() < k) pq.offer(num);
else if(num < pq.peek()){
pq.poll();
pq.offer(num);
}
}
//遍歷pq取出所有元素到res陣列
int[] res = new int[k];
int idx = 0;
for(int num:pq){
res[idx++] = num;
}
return res;
}
}
用lambda表示式可以過載PriorityQueue中的compare方法。以下是一些相關的參考文章。
lambda表示式
PriorityQueue
複雜度分析
時間複雜度:
空間複雜度:,只使用了大小為k的優先佇列