数组中的第K个最大元素
问题描述:在未排序的数组中找到第K个最大的元素。
解题思路:可以使用最小堆(优先队列)来解决这个问题。将数组中的元素依次加入最小堆中,当堆的大小超过K时,就弹出堆顶元素(即当前最小的元素)。最后,堆顶元素即为第K个最大的元素。
Java代码实现(这里使用Java的PriorityQueue
实现最小堆):
在这段代码中,KthLargest
类实现了一个方法来找到数组中的第 k
大元素。这个方法利用了 Java 的 PriorityQueue
类,但这里有一个小技巧:尽管我们通常将 PriorityQueue
用作最大堆或最小堆,但在这里它被用作一个大小为 k
的最小堆来存储当前遇到的最大的 k
个数。这样,堆顶(peek
方法返回的元素)始终是这些数中最小的,但由于我们只关心最大的 k
个数,所以堆顶的元素实际上就是第 k
大的元素。以下是添加了注释的代码:
/**
* 找到数组中第k大的元素
* @param nums 输入的整数数组
* @param k 需要找到的第k大的元素的序号,注意这里的k是从1开始计数的
* @return 数组中第k大的元素
*/
class Solution {
public int findKthLargest(int[] nums, int k) {
// 小顶堆,堆顶是最小元素
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int e : nums) {
// 每个元素都要过一遍二叉堆
pq.offer(e);
// 堆中元素多于 k 个时,删除堆顶元素
if (pq.size() > k) {
pq.poll();
}
}
// pq 中剩下的是 nums 中 k 个最大元素,
// 堆顶是最小的那个,即第 k 个最大元素
return pq.peek();
}
}
// 详细解析参见:
// https://labuladong.online/algo/slug.html?slug=xx4gT2
需要注意的是,这里的 k
是从 1 开始计数的,而不是从 0 开始。另外,虽然 PriorityQueue
的初始容量被设置为 k
,但这并不意味着堆的大小会一直被限制在 k
。实际上,在填充堆的初始阶段,堆的大小会随着元素的加入而增长,直到达到 k
个元素。之后,只有当遇到比堆顶元素还要大的元素时,堆顶元素才会被移除,新元素才会被加入堆中,从而保持堆中始终有 k
个当前遍历过的最大元素。
当然,除了使用最小堆(PriorityQueue
)之外,还有其他几种方法可以实现找到数组中的第k
大元素。以下是一些常见的替代方法:
1. 排序
最直接的方法是首先对数组进行排序(升序或降序都可以,但降序更直观),然后直接返回第k
个元素(注意数组索引从0开始,所以实际上是第k-1
个位置上的元素)。但这种方法的时间复杂度通常是O(n log n)
,其中n
是数组的长度,因为排序操作通常需要这个时间复杂度。
import java.util.Arrays;
public class KthLargest {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums); // 对数组进行排序
return nums[nums.length - k]; // 返回第k大的元素
}
}
2. 快速选择(QuickSelect)
快速选择算法是快速排序算法的一个变种,用于在未完全排序的数组中找到第k
小(或第k
大)的元素。它的平均时间复杂度为O(n)
,但最坏情况下仍然是O(n^2)
。快速选择通过选择一个“枢轴”(pivot)元素,将数组分为两部分,一部分包含所有小于枢轴的元素,另一部分包含所有大于枢轴的元素,然后递归地在包含第k
个元素的那一部分继续搜索。
public class KthLargest {
public int findKthLargest(int[] nums, int k) {
return quickSelect(nums, 0, nums.length - 1, nums.length - k);
}
private int quickSelect(int[] nums, int left, int right, int k) {
if (left == right) return nums[left];
int pivotIndex = partition(nums, left, right);
if (k == pivotIndex) {
return nums[k];
} else if (k < pivotIndex) {
return quickSelect(nums, left, pivotIndex - 1, k);
} else {
return quickSelect(nums, pivotIndex + 1, right, k);
}
}
// 这里省略了partition方法的实现,它负责将数组按照某个枢轴元素进行分区
}
3. 部分排序
在某些情况下,你可以使用部分排序算法(如堆排序的部分版本或基于比较的排序算法的部分执行)来找到第k
大的元素,而不需要对整个数组进行排序。这种方法的时间复杂度可能介于O(n log k)
和O(n log n)
之间,具体取决于实现方式。
4. 使用最大堆
虽然题目中使用了最小堆,但你也可以使用最大堆来存储当前遇到的最大的k
个数。这样,堆顶元素将始终是最大的元素,而你需要找到的是第k
大的元素,所以你需要从堆中移除堆顶元素(即最大元素)k-1
次,然后堆顶元素就是第k
大的元素。但这种方法相比最小堆来说效率较低,因为它需要额外的k-1
次移除操作。
5. 线性选择算法(针对特定情况)
对于某些特定情况(如数组中的元素范围很小或具有其他特殊性质),可能存在线性时间复杂度的选择算法。但这些算法通常依赖于数组的特定性质,并不适用于一般情况。
综上所述,选择哪种方法取决于具体的应用场景和对时间复杂度的要求。在大多数情况下,快速选择算法是一个很好的选择,因为它在平均情况下具有线性时间复杂度。
标签:堆顶,nums,int,元素,算法,数组,排序 From: https://blog.csdn.net/qq_36329049/article/details/142469091