给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
1 2
| 输入: [3,2,1,5,6,4], k = 2 输出: 5
|
示例 2:
1 2
| 输入: [3,2,3,1,2,4,5,5,6], k = 4 输出: 4
|
提示:
1 <= k <= nums.length <= 105
-104 <= nums[i] <= 104
快速选择
1.随机选取一个元素作为中枢划分数组,中枢左边元素比中枢值大,中枢右边元素比中枢值小
2.如果划分后,中枢元素下标为k-1,说明中枢正好是第k大元素
3.否则,中枢元素下标<k-1,第k大元素在右半区间,右边区间重复1操作
4.中枢元素下标>k-1,第k大元素在左边区间,左边区间重复1操作
注意:使用填坑法对nums进行划分时,需要将nums[left]放到随机的pivot_index上面,因为nums[left]会最先被替换掉。
而传统的快速排序因为把nums[left]作为pivot,所以不需要这一步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Solution { public int findKthLargest(int[] nums, int k) { return partition(nums, k, 0, nums.length-1); }
public int partition(int[] nums , int k, int left , int right){ int pivot_index = new Random().nextInt(right-left + 1) + left; int pivot = nums[pivot_index]; nums[pivot_index] = nums[left]; int i = left, j = right; while(i < j){ while(i < j && nums[j] <= pivot) j--; nums[i] = nums[j]; while(i < j && nums[i] >= pivot) i++; nums[j] = nums[i]; } nums[i] = pivot; if(i < k-1){ return Partition(nums, k, i+1, right); }else if(i > k-1){ return Partition(nums, k, left, i-1); } return nums[i]; } }
|
时间复杂度:$O(n)$,引入随机化进行数组划分,它的时间代价的期望是 $O(n)$
空间复杂度:$O(logn)$,递归使用栈空间的空间代价的期望为 $O(logn)$
堆排序
建立大根堆,删除k-1次元素后,堆顶元素为第k大元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Solution { public int findKthLargest(int[] nums, int k) { int len = nums.length; buildHeap(nums, len); for(int i = 0 ; i < nums.length; i++){ System.out.println(nums[i]); } for(int i = len-1; i >= len-(k-1) ;i--){ swap(nums, 0, i); adjustHeap(nums, 0, i); } return nums[0]; }
public void buildHeap(int[] nums, int len){ for(int i = len/2-1 ; i >= 0; i--){ adjustHeap(nums, i, len); } }
public void adjustHeap(int[] nums, int k, int len){ while(2 * k + 1 < len){ int i = 2 * k + 1; if(i + 1 < len && nums[i] < nums[i+1]){ i++; } if(nums[k] < nums[i]){ swap(nums, k, i); k = i; }else{ break; } } } public void swap(int[] nums, int i, int j){ int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
|
时间复杂度:$O(nlogn)$,建堆的时间代价是 $O(n)$,删除k-1次,每次删除向下调账时间代价为logn,删除的总代价是 $O(klogn)$,因为 k<n,故渐进时间复杂为 $O(n+klogn)=O(nlogn)$
空间复杂度:$O(logn)$,即递归使用栈空间的空间代价