题目描述
有一个整数数组,请你根据快速排序的思路,找出数组中第K大的数。
给定一个整数数组a,同时给定它的大小n和要找的K(K在1到n之间),请返回第K大的数,保证答案存在。
扩展思考:如何处理数组中的重复元素?比如,对于数组a={1,3,5,2,2},第四大的元素应该是2还是1呢?
本文作这种分类:
如果第四大的元素是2,说明在处理第k大的元素时不处理重复的数据,也就是将原数组进行降序排序后,下标为k-1的元素。这种处理方法称之为“不处理重复数据方法”;
如果第四大的元素是1,说明已经忽略重复的数据了,这时候需要首先对a进行去重处理(可以用哈希表),然后再进行讨论。这种处理方法称之为“去除重复数据方法”。
下面的方法都默认为第一种。
分治法
快速排序使用了分治法的策略。它的基本思想是,选择一个基准数(一般称之为枢纽元),通过一趟排序将要排序的数据分割成独立的两部分:在枢纽元左边的所有元素都不比它大,右边所有元素都比它大,此时枢纽元就处在它应该在的正确位置上了。
在本问题中,假设有N个数存储在数组a中。我们从a中随机找出一个元素作为枢纽元,把数组分为两部分。其中左边元素都不比枢纽元大,右边元素都不比枢纽元小。此时枢纽元所在的位置记为mid。
如果右半边(包括a[mid])的长度恰好为k,说明a[mid]就是需要的第k大元素,直接返回a[mid]。
如果右半边(包括a[mid])的长度大于k,说明要寻找的第k大元素就在右半边,往右半边寻找。
如果右半边(包括a[mid])的长度小于k,说明要寻找的第k大元素就在左半边,往左半边寻找。
public class FindKth {
public static void main(String[] args) {
// [1,3,5,2,2],5,3
int arr[] = {1,3,5,2,2};
datastructure.FindKth f = new datastructure.FindKth();
int result = f.findKth(arr, 5, 1);
System.out.println(result);
for(int t : arr){
System.out.print(t+" ");
}
System.out.println("");
}
public int findKth(int[] a, int n, int k) {
return find(a, 0,n-1, k);
}
public int find(int[] a, int start, int end, int k){
// 比第一个元素大的放左边,小的放右边
int p = partition(a, start, end);
if(p+1 < k){
return find(a, p+1,end, k);
}
if(p+1 > k){
return find(a, start, p-1, k);
}
return a[p];
}
public int partition(int[] a, int start, int end){
int i = start;
int j = end + 1;
int V = a[start];
while(true){
while(a[++i] > V){ // 直到比V小
if(i>=end) break;
}
while(a[--j] < V){// 直到比V大
if(j<=start) break;
}
if(i >= j)break;
// 交换i,j位置的值
exchange(a,i,j);
}
exchange(a,start,j);
return j;
}
private void exchange(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}