题目:
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
简单回顾快速排序
原序列是[4,1,5,3,6,2,7,8]
直接上图片和代码
a图选取3作为中间值,将其交换到数的尾部,初始化p1指针到下标为-1的位置,指针p2至下标为0的位置,b图右移指针p2直到遇到第一个比3小的数字1,指针p1右移一格然后交换指针p1和p2指向的数字,c图同理,d图右移指针p2直到指向数字3,指向p1右移一格然后交换指针p1和p2指向的数字。
快速排序时间复杂度取决于所选取的中间值在数组中的位置,如果每次选取的中间值在排序数组中都接近于数组中间的位置,那么快速排序的时间复杂度是O(nlogn),如果每次选取的中间值都位于排序数组的头部或尾部,那么快速排序的时间复杂度为O(n^2)。在随机选取中间值的前提下,快速排序的平均时间复杂度为O(nlogn),是非常高效的排序算法。
import java.util.Random;
//快速排序 基本思想是分治法
public class SortArray02 {
public int[] sortArray(int[] nums){
quicksort(nums,0,nums.length-1);
return nums;
}
public void quicksort(int[] nums, int start, int end) {
if (end > start){
int pivot = partition(nums,start,end);
quicksort(nums,start,pivot-1);
quicksort(nums,pivot+1,end);
}
}
private int partition(int[] nums, int start, int end) {
int random = new Random().nextInt(end - start+1)+start;
swap(nums,random,end);
int small = start-1;
for (int i = start; i <end; i++) {
if (nums[i] < nums[end]){
small++;
swap(nums,i,small);
}
}
small++;
swap(nums,small,end);
return small;
}
private void swap(int[] nums,int index1,int index2){
if (index1 !=index2){
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
}
分析该题:
剑指offer第59题中介绍过一种基于最小堆的解法,详细请看该题,从数据流中读取n个数字并找出第k大的数字的时间复杂度是O(nlogk),空间复杂度O(k)。
59题中的数据位于一个数据流中,不能一次性将所有数据全部读入内存,而本题不一样,数据都保存在一个数组中,所有操作都在内存中完成,我们有更快找出第k大的数字的算法。
快速排序一次排序就能确定(固定)一个数字的顺序,在长度为n的排序数组中,第k大的数字的下标是n-k,可以利用快速排序的partition函数对数组进行分区,如果函数partition选取的中间值在分区之后的下标正好是n-k,分区后左边的值都比中间值小,右边的值都比中间值大,即使整个数组不是排序的,中间值也肯定是第k大的数字。
根据这种思路,我们来解这道题。
由于函数partition随机选择中间值,因此返回值也具有随机性,计算这种算法的时间复杂度需要概率相关知识,此处仅计算一个特定场合下的时间复杂度,假设每次选择的中间值都位于分区后的数组的中间位置,那么第一次函数扫描长度为n的数组,第二次函数扫描长度为n/2的子数组,第三次扫描长度为n/4的数组,重复该过程,直到子数组为1,因为n+n/2+n/4…+1=2n,因此总的时间复杂度为O(n)。
代码:
import java.util.Random;
public class FindKthLargest {
public static void main(String[] args) {
int[] nums = {3,1,2,4,5,5,6};
int k = 3;
FindKthLargest findKthLargest = new FindKthLargest();
int kthLargest = findKthLargest.findKthLargest(nums, k);
System.out.println(kthLargest);
}
public int findKthLargest(int[] nums, int k) {
int target = nums.length - k;
int start = 0;
int end = nums.length - 1;
int index = partition(nums, start, end);
while (index != target) {
if (index != target) {
end = index - 1;
} else {
start = index + 1;
}
index = partition(nums, start, end);
}
return nums[index];
}
private int partition(int[] nums, int start, int end) {
int random = new Random().nextInt((end - start)+1) + start;
swap(nums, random, end);
int small = start - 1;
for (int i = start; i < end; i++) {
if (nums[i] < nums[end]) {
small++;
swap(nums, i, small);
}
}
small++;
swap(nums, small, end);
return small;
}
private void swap(int[] nums, int index1, int index2) {
if (index1 != index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
}