215.数组中的第K个最大元素
题目
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
方法一(排序)
1 2 3 4
| public int findKthLargest(int[] nums, int k) { Arrays.sort(nums); return nums[nums.length - k]; }
|
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
方法二(堆)
维护一个大小为K的小根堆。遍历数组
- 当堆中元素不足k个时,直接将元素加入堆中
- 当堆中元素大于等于k个时:
- 如果当前元素比堆顶元素大,那么它就比堆顶元素更有资格位列K个最大的元素,因此让堆顶弹出,让它进来。
- 如果当前元素比堆顶元素小,则不做任何操作。保证堆中元素是遍历到当前为止K个最大的元素。
最后,堆顶元素即为第K个最大的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> heap = new PriorityQueue<>(); for(int num : nums){ if(heap.size() < k) heap.add(num); else{ if(num > heap.peek()){ heap.poll(); heap.add(num); } } } return heap.peek(); }
|
- 时间复杂度:O(NlogK)
- 空间复杂度:O(K)
方法三(快速选择)
要找倒数第k个元素,也即找到正着数第len - k + 1个元素。假如对数组进行排序,这个元素应该位于len - k 个位置
先对整个数组num进行partition操作
- 如果第len - k个位置正好处在等于区域,那么等于区域的值就是要找的元素
- 如果第len - k个位置位于等于区域右边,则需要在大于区域寻找nums中第len - k + 1小的元素
- 如果第len - k个位置位于等于区域左边,则需要在小于区域寻找nums中第len - k + 1小的元素
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
| public int findKthLargest(int[] nums, int k) { return quickSelect(nums, 0, nums.length - 1, nums.length - k ); }
public int quickSelect(int[] nums, int start, int end, int i){ if(start == end) return nums[start]; int[] pivotRange = partition(nums, start, end); if(i >= pivotRange[0] && i <= pivotRange[1]) return nums[i]; else if(i > pivotRange[1]) return quickSelect(nums, pivotRange[1] + 1, end, i); else return quickSelect(nums, start, pivotRange[0] - 1, i); }
public int[] partition(int[] nums, int start, int end){ int randomIndex = (int)Math.random() * (end - start + 1) + start; int pivot = nums[randomIndex]; int less = start - 1; int more = end + 1; int cur = start; while(cur < more){ if(nums[cur] < pivot) swap(nums, ++less, cur++); else if(nums[cur] > pivot) swap(nums, --more, cur); else cur++; } return new int[]{less + 1, more - 1}; }
public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
|
方法二和方法三的对比
很明显,快速选择算法有更好的时间和空间复杂度。但是其也有一些缺点:
- 快速选择算法需要给出所有的数据,才能运行算法。而堆可以对于给定的输入流动态地处理,只需要维护一个大小为k的堆,输入流中的数据来一个处理一个,就可以保证堆中总是输入流中最小的k个数
- 快速选择排序需要修改原数组。