题目描述
给定一个数组a和数字k,找出a中第k大的数。
方法一:快排思想
找到一个枢轴,枢轴右边还有k-1个数即可。因为每次遍历只选择一边,因而降低了时间复杂度。
public class Main {
public static int kthBiggest (int[]a,int k,int low,int high) {
int left=low,right=high,pivot=a[left];
while(left<right){
while(left<right&&a[right]>=pivot) right--;
if(left<right) {
a[left] = a[right];
left++;
}
while(left<right&&a[left]<=pivot) left++;
if(left<right) {
a[right] = a[left];
right--;
}
}
a[left]=pivot;
if(high-left>k-1) return kthBiggest(a,k,left+1,high);
else if(high-left<k-1) return kthBiggest(a,k-(high-left+1),low,left-1);
else return pivot;
}
public static void main(String[] args) {
int[] a={3,5,4,8,9,1,0,7,2,6};
System.out.println(kthBiggest(a,4,0,a.length-1));
}
}
时间复杂度分析:时间复杂度为O(n)。第一次遍历长度为n,接下来每一次遍历的长度都是上一次的长度乘以一个随机比例。
方法二:基于冒泡排序和简单选择排序
并不需要将完整的数组排完序。选择这两种排序算法的原因是它们都是先选择数组中最大的数,然后是次大的数,以此类推。因此外层循环只用执行k次即可达到目标。
冒泡法:
public static int bubble (int[]a,int k) {
int n=a.length;
for(int i=0;i<k;++i)
for(int j=n-1;j>i;--j){
if(a[j]>a[j-1]){
int temp=a[j];
a[j]=a[j-1];
a[j-1]=temp;
}
}
return a[k-1];
}
选择法:
public static int select(int[] a,int k){
int n=a.length,m,i,j;
for(i=0;i<k;++i) {
m=i;//m为第i轮遍历剩余元素中最大元素的下标
for (j = i + 1; j < n; ++j) {
if(a[j]>a[m]) m=j;
}
int temp=a[j];
a[j]=a[m];
a[m]=temp;
}
return a[k-1];
}
时间复杂度分析:两种算法时间复杂度均为O(n*k)。
方法三:自行构建最大堆
如果使用函数库中的优先队列,则需要更高的空间复杂度。可以在自行构建最大堆,在原数组自身上操作。另外也没有必要将整个数组排好序,堆排序也是依次选出剩余元素中的最大值,执行k次该操作即可。
因为原数组下标从0开始,所以根节点下标为0。有:i结点的左孩子2i+1,右孩子2i+2, index结点的父亲:(index-1)/2。
//堆调整操作,用于建堆
//cur为当前节点位置,n为当前堆中的元素个数-1
public static void adjust(int[]a,int cur,int n){
int temp=a[cur];
for(int i=cur*2+1;i<=n;i=2*i+1){
if(i<n&&a[i]<a[i+1]) i++;
if(a[i]<=temp) break;
a[cur]=a[i];
cur=i;
}
a[cur]=temp;
}
public static int heapKth(int[] a,int k){
int n=a.length-1;
//将元素组构建为堆
//最后一个节点下标为n,最后一个非叶节点下标为(n-1)/2。
for(int i=(n-1)/2;i>=0;--i) adjust(a,i,n);
for(int i=n;i>n-k;--i){
//堆中剩余元素的最大值放到数组尾部。
//完全二叉树的最后一个节点移至堆顶。
int temp=a[i];
a[i]=a[0];
a[0]=temp;
//根节点的左右子树均为堆,将根节点调整到适当位置后,整棵树仍然是一个堆。
adjust(a,0,i-1);
}
return a[n-k+1];
}
时间复杂度分析:复杂度为O(nlogn),由建堆的过程决定。