【Leetcode】215.数组中的第K个最大元素

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 );
}
//在start到end的范围里,寻找nums中第i+1小的元素(正序后第i位置的元素)
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;
}
  • 时间复杂度:O(N)
  • 空间复杂度:O(1)

方法二和方法三的对比

很明显,快速选择算法有更好的时间和空间复杂度。但是其也有一些缺点:

  • 快速选择算法需要给出所有的数据,才能运行算法。而堆可以对于给定的输入流动态地处理,只需要维护一个大小为k的堆,输入流中的数据来一个处理一个,就可以保证堆中总是输入流中最小的k个数
  • 快速选择排序需要修改原数组。