给定整数数组 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
方法一:基于快速排序的选择方法
思路和算法
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
return qSort(nums, 0, nums.length - 1, nums.length - k);
}
//对数组中下标为 low - high 的子表进行快速排序
private int qSort(int[] nums, int low, int high, int k) {
int pivot;
//将子表一分为二,算出枢轴 pivot
pivot = partition(nums, low, high);
if (pivot == k) {
return nums[pivot];
} else {
if (pivot < k) {
//对高子表进行递归排序
return qSort(nums, pivot + 1, high, k);
} else {
//对低子表进行递归排序
return qSort(nums, low, pivot - 1, k);
}
}
}
private int partition(int[] nums, int low, int high) {
int pivotKey;
//用子表的第一个记录作为枢轴记录
pivotKey = nums[low];
//从表的两端交替向中间扫描
while (low < high) {
while (low < high && nums[high] >= pivotKey) {
high--;
}
//比枢轴小的记录交换到低端
swap(nums, low, high);
while (low < high && nums[low] <= pivotKey) {
low++;
}
//比枢轴大的记录交换到高端
swap(nums, low, high);
}
//返回枢轴所在的位置
return low;
}
//交换数组中的两个数
private void swap(int[] nums, int low, int high) {
int temp = nums[low];
nums[low] = nums[high];
nums[high] = temp;
}
}
复杂度分析
时间复杂度:O(n),如上文所述,证明过程可以参考「《算法导论》9.2:期望为线性的选择算法」。
空间复杂度:O(logn),递归使用栈空间的空间代价的期望为 O(logn)。
方法二:基于堆排序的选择方法
思路和算法
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
for (int i = 0; i < nums.length; i++) {
if (priorityQueue.size() < k) {
priorityQueue.offer(nums[i]);
} else {
if (priorityQueue.peek() < nums[i]) {
priorityQueue.poll();
priorityQueue.offer(nums[i]);
}
}
}
return priorityQueue.peek();
}
}
class Solution {
public int findKthLargest(int[] nums, int k) {
// 将带排序序列构建成一个小顶堆
for (int i = nums.length / 2; i >= 0; i--) {
headAdjust(nums, i, nums.length - 1);
}
//逐步将每个最小值的根节点与末尾元素交换,并且将 nums[1] 至 nums[i-1] 调整为小顶堆
for (int i = nums.length - 1; i > 0; i--) {
swap(nums, 0, i);
headAdjust(nums, 0, i - 1);
}
return nums[nums.length - k - 1];
}
/**
* 已知 nums[low] 至 nums[high] 中除了 nums[low] 之外均满足堆的定义
* 本方法调整 nums[low] ,使 nums[low] 至 nums[high] 成为一个小顶堆
*
* @param nums
* @param s
* @param m
*/
private void headAdjust(int[] nums, int s, int m) {
int temp = nums[s];
//沿关键字较大的孩子结点向下筛选
for (int j = 2 * s; j <= m; j *= 2) {
//j 为关键字中较大的记录的下标
if (j < m && nums[j] < nums[j + 1]) {
j++;
}
if (temp >= nums[j]) {
break;
}
//较大元素插入到它的根节点
nums[s] = nums[j];
//s 变为较大元素的下标
s = j;
}
//插入
nums[s] = temp;
}
//交换数组中的两个数
private void swap(int[] nums, int low, int high) {
int temp = nums[low];
nums[low] = nums[high];
nums[high] = temp;
}
}
复杂度分析
时间复杂度:O(nlogn),建堆的时间代价是 O(n),删除的总代价是 O(klogn),因为 k<n,故渐进时间复杂为 O(n+klogn)=O(nlogn)。
空间复杂度:O(logn),即递归使用栈空间的空间代价。