题目来自leetcode的215道题,寻找数组中的第K个最大值,如果要寻找一个数组的最大值,可能很简单,但是寻找第K个最大值,可能就不是那么简单了,当然这道题目有多个解法
第一种解法,排序法,想要找到第K大的元素,比较好的方法可能是排序法,经过排序的数组可以直接通过索引找到第K个值,这个值自然就是数组中第K大的值
在这个数组中,如果要寻找第2大的值,那么结果就是5
在这道题中,可以采用各种排序法,例如归并排序和快速排序等,很明显算法的时间复杂度是O(nlogn)
那么通过排序就能完成这道题目了
第二种解法,通过快速排序的思想,快速的完成这道题目,让时间复杂度降到O(n)级别的吧,性能更加的优秀。
快速排序的思想便是在第一遍排序过后,中间的值就是当前数组的中间值,左边都是小于它的值,右边则都是大于它的值
那么如果第一次排序后中间值的索引刚好等于k,那么代表已经找到这个值,直接返回中间值的数值即可。
但是如果中间值的索引大于k,那么代表k值在左边的区域,反之如果中间值的索引小于k,那么代表k值在右边的区域。需要注意这里的k索引并不是题目传进来的那一个k值,在算法开始的时候需要进行转换
有了这个信息后,就只需要寻找一边的元素就可以了,因为已经能确定k在哪一边。
代码如下
public int findKthLargest(int[] nums, int k) {
//请注意这里的k是需要转换的,因为是取第k大的数,所以传过来的k并不是要查的k
k = nums.length - k;
int result = quickSort(nums, 0, nums.length - 1,k);
return result;
}
private int quickSort(int[] nums, int l, int r,int k) {
//找到关键点p,此时索引p对应的值即是数组中的中间值
int p = partition(nums,l,r);
//递归终止条件,如果索引p等于k,那么代表索引p的值就是结果k值,那么直接返回索引p的值即可
if(p==k) {
return nums[p];
}
//如果p大于k,那么代表第k大的值在p的左边,此时左边的数组是未排序的,但能确定的是左边的值都比p的值要小
int result = 0;
if(p>k){
result = quickSort(nums,l,p-1,k);
}else {
//如果p小于等于k,那么代表第k大的值在p的右边,此时右边的数组是未排序的,但能确定的是右边的值都比p的值要大
result = quickSort(nums,p+1,r,k);
}
return result;
}
//返回值是下标p,p左边的元素都小于p的元素,p右边的元素都大于p的元素。试着用一次遍历,使用原地排序,不创建新的空间,来完成吧!
public int partition(int[] arr, int l, int r){
// 生成 [l, r] 之间的随机索引
int p = l + (new Random()).nextInt(r - l + 1);
swap(arr, l, p);
//记录开始的值
int j = l;
for (int i = l+1; i <= r; i++) {
//System.out.println(arr[i]);
if(arr[i]<=arr[l]){
j++;
//交换索引j和索引i的值
swap(arr,i,j);
}
}
//最后再将left元素与下标j元素交换就可以了
swap(arr,l,j);
return j;
}
public void swap(int[] arr,int i,int j){
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {3,2,1,5,6,4};
int k = 1;
System.out.println(findKthLargest(arr,k));
int[] arr2 = {3,2,3,1,2,4,5,5,6};
int k2 = 4;
System.out.println(findKthLargest(arr2,k2));
}
时间复杂度O(n)是怎么得到的?
进行第一次快排后,需要扫描整个数组一次,就是对n个元素进行操作,到后面只需要操作一边就可以了,但是因为快速排序是随机算法,两边的元素可能不均等,因此通过期望可以大概得出每一边的元素为n/2
因此在最坏的情况下
需要的时间大约为n+n/2+n/4+…+1 ≈ 2n,因此是O(n)级别的算法。
当然partition也可以选择双路快速排序
private int partition(int[] arr, int l, int r){
// 生成 [l, r] 之间的随机索引
int p = l + (new Random()).nextInt(r - l + 1);
swap(arr, l, p);
// arr[l+1...i-1] <= v; arr[j+1...r] >= v
int i = l + 1, j = r;
while(true){
while(i <= j && arr[i] < arr[l])
i ++;
while(j >= i && arr[j] > arr[l])
j --;
if(i >= j) break;
swap(arr, i, j);
i ++;
j --;
}
swap(arr, l, j);
return j;
}