Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
For example,
Given [3,2,1,5,6,4] and k = 2, return 5.
Note:
You may assume k is always valid, 1 ≤ k ≤ array's length.
在數(shù)組中找到第k大的元素
注意事項
你可以交換數(shù)組中的元素的位置
樣例
給出數(shù)組 [9,3,2,4,8],第三大的元素是 4
給出數(shù)組 [1,2,3,4,5],第一大的元素是 5,第二大的元素是 4,第三大的元素是 3,以此類推
分析
這個一個經(jīng)典的尋找第k大數(shù)的問題,我們可以有很多種解法,下面我們一一介紹。
1直接排序
顯然最簡單的思想就是排序,然后取出倒數(shù)第k個元素就可以了,我們可以直接調(diào)用內(nèi)部的排序函數(shù)。
public int findKthLargest(int[] nums, int k) {
final int N = nums.length;
Arrays.sort(nums);
return nums[N - k];
}
** O(N lg N) running time + O(1) memory **
在用例不多,數(shù)據(jù)量不大的時候,這種簡單的方法,效率反而很高,超過大部分算法
2 利用堆
從第k大元素我們自然想到堆的性質(zhì),我們可以維護一個只有k個元素的最小堆,遍歷一遍所有元素,組后留下的k個就是前k大的元素
public int findKthLargest(int[] nums, int k) {
final PriorityQueue<Integer> pq = new PriorityQueue<>();
for(int val : nums) {
pq.offer(val);
if(pq.size() > k) {
pq.poll();
}
}
return pq.peek();
}
** O(N lg K) running time + O(K) memory **
3 快排思想
利用快排的partiton思想,這種方法在最好情況下,可以達到線性時間,但是如果輸入是有序的,則是最壞情況,會達到平方時間的復(fù)雜度
public int findKthLargest(int[] nums, int k) {
k = nums.length - k;
int lo = 0;
int hi = nums.length - 1;
while (lo < hi) {
final int j = partition(nums, lo, hi);
if(j < k) {
lo = j + 1;
} else if (j > k) {
hi = j - 1;
} else {
break;
}
}
return nums[k];
}
private int partition(int[] a, int lo, int hi) {
int i = lo;
int j = hi + 1;
while(true) {
while(i < hi && less(a[++i], a[lo]));
while(j > lo && less(a[lo], a[--j]));
if(i >= j) {
break;
}
exch(a, i, j);
}
exch(a, lo, j);
return j;
}
private void exch(int[] a, int i, int j) {
final int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
private boolean less(int v, int w) {
return v < w;
}
可以看到由于最壞情況的存在,所以其實效率并不理想
4改進快排
我們考慮改進上面的快排算法,我們知道當數(shù)據(jù)有序時,會產(chǎn)生最壞情況,那么我們就隨機化輸入的數(shù)據(jù),這樣可以盡量避免最壞情況的發(fā)生。
public class Solution {
public int findKthLargest(int[] nums, int k) {
shuffle(nums);
k = nums.length - k;
int lo = 0;
int hi = nums.length - 1;
while (lo < hi) {
final int j = partition(nums, lo, hi);
if(j < k) {
lo = j + 1;
} else if (j > k) {
hi = j - 1;
} else {
break;
}
}
return nums[k];
}
private void shuffle(int a[]) {
final Random random = new Random();
for(int ind = 1; ind < a.length; ind++) {
final int r = random.nextInt(ind + 1);
exch(a, ind, r);
}
}
private int partition(int[] a, int lo, int hi) {
int i = lo;
int j = hi + 1;
while(true) {
while(i < hi && less(a[++i], a[lo]));
while(j > lo && less(a[lo], a[--j]));
if(i >= j) {
break;
}
exch(a, i, j);
}
exch(a, lo, j);
return j;
}
private void exch(int[] a, int i, int j) {
final int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
private boolean less(int v, int w) {
return v < w;
}
}
可以看到算法果然改進了不少