1、题目
Leetcode 215. Kth Largest Element
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
![](https://img.haomeiwen.com/i28792356/d82260a56d7a632f.png)
2、思路
「快速选择」:Top K 问题的最优解
「最小堆」:
「数组计数+统计」:利用数组下标进行计数排序
3、快速选择和最小堆的 Java 代码
public class LeetCode_275_Solution {
Random random = new Random();
public int findKthLargest(int[] nums, int k) {
// 要找到的元素所在索引: 前K大,即倒数索引第K个
int index = nums.length - k;
int right = nums.length - 1;
int left = 0;
return quickSelect(nums, left, right, index);
}
public int quickSelect(int[] nums, int left, int right, int index) {
// 得到分区值索引q
int q = randomPartition(nums, left, right);
if (q == index) {
// 如果刚好索引q就是想要的索引,则直接返回
return nums[q];
} else {
// 如果不是,比较q 与 index ,确定下次要检索的区间, 要么是[q+1, right], 要么就是[left, q-1]
return q < index ? quickSelect(nums, q + 1, right, index) : quickSelect(nums, left, q - 1, index);
}
}
public int randomPartition(int[] nums, int l, int r) {
// 1. 随机数范围: [0, r-l+1) 同时加l, 则是 [l, r+1) = [l, r] 也就是在这个[l,r] 中随机选一个索引出来
int i = random.nextInt(r - l + 1) + l;
// 2. 交换nums[i], nums[r], 也就是将随机数先放在[l,r]最右边nums[r]上
swap(nums, i, r);
return partition(nums, l, r);
}
public int partition(int[] nums, int l, int r) {
// 3. 在调用当前方法的randomPartition方法中,已经确定了了随机数是nums[r]
int x = nums[r], i = l - 1;
// 首先比较区间在[l, r]之间, 所以nums[j]中的 l<= j <= r
for (int j = l; j < r; ++j) {
// 4. nums[j] 跟随机数 x 比较, 小于x的数都跟[l,r]左边区间交换,i=l-1,所以++i=l,初始索引就是l,
if (nums[j] <= x) {
swap(nums, ++i, j);//两两交换
}
}// 这个for循环操作就是将小于 x 的数都往[i, j]的左边区间设置,从而实现存在[l, i]区间,使得对应数值都 小于 x
//5. 既然已经将<x的值都放在一边了,现在将x也就是nums[r] 跟nums[i+1]交换,从而分成两个区间[l.i+1]左, [i+2, r]右,左边区间的值都小于x
swap(nums, i + 1, r);
// 然后返回这个分区值
return i + 1;
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
for 循环里面判断小顶堆里面的 size() 是否大于 k 个数,是的话就 poll() 出去;整个 for 循环结束之后剩下来的就是 k 个数的小顶堆。堆顶即第 k 大的数。
public int findKthLargest2(int[] nums, int k) {
PriorityQueue<Integer> heap = new PriorityQueue<>();
for (int num : nums) {
heap.add(num);
if (heap.size() > k) {
heap.poll();
}
}
return heap.peek();
}
JAVA自建大根堆
class Solution {
public int findKthLargest(int[] nums, int k) {
int heapSize = nums.length;
buildMaxHeap(nums, heapSize);
//建堆完毕后,nums【0】为最大元素。逐个删除堆顶元素,直到删除了k-1个。
for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
//先将堆的最后一个元素与堆顶元素交换,由于此时堆的性质被破坏,需对此时的根节点进行向下调整操作。
swap(nums, 0, i);
//相当于删除堆顶元素,此时长度变为nums.length-2。即下次循环的i
--heapSize;
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
public void buildMaxHeap(int[] a, int heapSize) {
//从最后一个父节点位置开始调整每一个节点的子树。数组长度为heasize,因此最后一个节点的位置为heapsize-1,所以父节点的位置为heapsize-1-1/2。
for (int i = (heapSize-2)/ 2; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
}
public void maxHeapify(int[] a, int i, int heapSize) { //调整当前结点和子节点的顺序。
//left和right表示当前父节点i的两个左右子节点。
int left = i * 2 + 1, right = i * 2 + 2, largest = i;
//如果左子点在数组内,且比当前父节点大,则将最大值的指针指向左子点。
if (left < heapSize && a[left] > a[largest]) {
largest = left;
}
//如果右子点在数组内,且比当前父节点大,则将最大值的指针指向右子点。
if (right < heapSize && a[right] > a[largest]) {
largest = right;
}
//如果最大值的指针不是父节点,则交换父节点和当前最大值指针指向的子节点。
if (largest != i) {
swap(a, i, largest);
//由于交换了父节点和子节点,因此可能对子节点的子树造成影响,所以对子节点的子树进行调整。
maxHeapify(a, largest, heapSize);
}
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
参考文章:
https://leetcode.cn/problems/kth-largest-element-in-an-array/solution/shu-zu-zhong-de-di-kge-zui-da-yuan-su-by-leetcode-/
https://www.jianshu.com/p/ba148b18974f
网友评论