215. 数组中的第K个最大元素 - 力扣(LeetCode)
给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4],
k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6],
k = 4
输出: 4
提示:
1 <= k <= nums.length <= 105
-104 <= nums[i] <= 104
方法思路
要找到数组中第k个最大的元素,可以使用快速选择算法,该算法基于快速排序的分区思想,能够在平均O(n)的时间复杂度内解决问题。具体步骤如下:
- 随机选择基准值(pivot):为了避免最坏情况,随机选择一个元素作为基准值。
- 三向分区:将数组分为三部分:小于基准值的元素、等于基准值的元素和大于基准值的元素。
- 递归处理:根据k的值决定在哪一部分继续查找。如果k小于等于右边区域的元素数目,则在右边区域继续查找;如果k在中间区域的范围内,则基准值即为答案;否则在左边区域继续查找。
解决代码
import java.util.Random;
class Solution {
private static final Random random = new Random();
public int findKthLargest(int[] nums, int k) {
return quickSelect(nums, 0, nums.length - 1, k);
}
private int quickSelect(int[] nums, int left, int right, int k) {
if (left == right) {
return nums[left];
}
int pivotIndex = left + random.nextInt(right - left + 1);
int pivot = nums[pivotIndex];
int lt = left; // 小于pivot的区域的右边界
int gt = right; // 大于pivot的区域的左边界
int i = left;
while (i <= gt) {
if (nums[i] > pivot) {
swap(nums, i, gt--);
} else if (nums[i] < pivot) {
swap(nums, i++, lt++);
} else {
i++;
}
}
// 此时数组分为三部分: [left..lt-1] < pivot,[lt..gt] == pivot,[gt+1..right] > pivot
int leftCount = lt - left; // 小于pivot的元素数量
int rightCount = right - gt; // 大于pivot的元素数量
int midCount = gt - lt + 1; // 等于pivot的元素数量
if (k <= rightCount) {
return quickSelect(nums, gt + 1, right, k);
} else if (k > rightCount + midCount) {
return quickSelect(nums, left, lt - 1, k - (rightCount + midCount));
} else {
return pivot;
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
代码解释
- 随机选择基准值:使用
Random
类生成随机索引,确保基准值的随机性,避免最坏情况。 - 三向分区:通过三个指针
lt
、gt
和i
将数组分为三部分。lt
跟踪小于基准值的元素右边界,gt
跟踪大于基准值的元素左边界,i
用于遍历数组。 - 递归处理:根据k的大小决定递归处理的部分:
- 如果k小于等于右边区域的元素数目,递归处理右边区域。
- 如果k大于右边和中间区域的元素数目之和,递归处理左边区域,并调整k的值。
- 否则,基准值即为所求。
该方法通过快速选择算法高效地找到第k大的元素,保证了平均时间复杂度为O(n)