问题
在未排序的数组中找到第 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 ≤ 数组的长度。
思路一
在快速排序中,每次都会进行partition,而每次partition都找到了一个数p,使得左边的数都小于p,右边的数都大于p。假设是从小到大排序。
完全可以按照从大到小的顺序进行排序。这样每次partition都可以得到一个数p,使得左边的数都大于p,右边的数都小于p。假设p的下标是index,那么p就是第index + 1的数。
代码一
class Solution {
public int findKthLargest(int[] nums, int k) {
int low = 0, high = nums.length - 1;
int partition;
do {
partition = partition(nums, low, high);
if (partition > k - 1) {
high = partition - 1;
} else {
low = partition + 1;
}
} while (partition != k - 1);
return nums[partition];
}
private int partition(int[] nums, int low, int high) {
if (low == high) {
return low;
}
int t = nums[low];
int index = low;
for (int i = low + 1; i <= high; i++) {
if (nums[i] > t) {
int temp = nums[++index];
nums[index] = nums[i];
nums[i] = temp;
}
}
int temp = nums[index];
nums[index] = nums[low];
nums[low] = temp;
return index;
}
}
时间复杂度:O(nlogn)。可能通过证明计算,可以是O(n)
空间复杂度:O(1)。
update 20220415
public int findKthLargest(int[] nums, int k) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int p = partition(nums, left, right);
if (p == nums.length - k) {
return nums[p];
} else if (p > nums.length - k) {
right = p - 1;
} else {
left = p + 1;
}
}
return -1;
}
private int partition(int[] nums, int p, int q) {
int lastIndex = p;
// 左边的都小
for (int i = p+1; i <= q; i++) {
if (nums[i] < nums[p]) {
lastIndex++;
swap(nums, lastIndex, i);
}
}
swap(nums, p, lastIndex);
return lastIndex;
}
private void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
代码二(官方版)
class Solution {
// 很神奇的地方
Random random = new Random();
public int findKthLargest(int[] nums, int k) {
return quickSelect(nums, 0, nums.length - 1, nums.length - k);
}
public int quickSelect(int[] a, int l, int r, int index) {
int q = randomPartition(a, l, r);
if (q == index) {
return a[q];
} else {
return q < index ? quickSelect(a, q + 1, r, index) : quickSelect(a, l, q - 1, index);
}
}
public int randomPartition(int[] a, int l, int r) {
// 很神奇的地方
int i = random.nextInt(r - l + 1) + l;
swap(a, i, r);
return partition(a, l, r);
}
public int partition(int[] a, int l, int r) {
int x = a[r], i = l - 1;
for (int j = l; j < r; ++j) {
if (a[j] <= x) {
swap(a, ++i, j);
}
}
swap(a, 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;
}
}
官方代码中加入了随机,使得运行代码只有2ms。如果把随机代码去掉就是21ms。代码一的执行时间是11ms。
时间复杂度:O(n)。官方参考了《算法导论》的证明。
空间复杂度:O(logn)。主要是有递归。
思路二
完全可以使用java的优先队列实现仅有k个元素的最小堆。这样堆顶就是第k大的数。
代码三
class Solution {
public int findKthLargest(int[] nums, int k) {
Queue<Data> queue = new PriorityQueue<Data>();
for (int i = 0; i < k; i++) {
queue.add(new Data(nums[i]));
}
for (int i = k; i < nums.length; i++) {
Data data = queue.peek();
if (data.val < nums[i]) {
queue.poll();
queue.add(new Data(nums[i]));
}
}
return queue.poll().val;
}
private class Data implements Comparable<Data> {
int val;
Data(int val) {
this.val = val;
}
public int compareTo(Data that) {
return this.val - that.val;
}
}
}
上面的代码执行时间是4ms。
时间复杂度:O(nlogk)
空间复杂度:O(k)
update 20220415
class Solution {
public int findKthLargest(int[] nums, int k) {
Queue<Integer> queue = new PriorityQueue<>();
for (int i = 0; i < k; i++) {
queue.offer(nums[i]);
}
for (int i = k; i < nums.length; i++) {
int peek = queue.peek();
if (nums[i] > peek) {
queue.poll();
queue.offer(nums[i]);
}
}
return queue.peek();
}
}
硬广告
欢迎关注公众号:double6
final
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情