目录
PriorityQueue 比较器 (comparator)
题目描述
给定整数数组 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 <= 10^5
- -10^4 <= nums[i] <= 10^4
思路 1:sort 排序
- 时间复杂度 O(NlogN),空间复杂度 O(1)
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length - k];
}
思路 2:优先队列
- 时间复杂度 O(NlogK),空间复杂度 O(K)
- 在优先队列中,元素将按升序从队列中移除
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue();
for (int val : nums) {
pq.add(val);
if (pq.size() > k) // 维护堆的大小为 K
pq.poll();
}
return pq.peek();
}
Java PriorityQueue
- PriorityQueue 类提供堆数据结构的功能,它实现了 Queue接口。
- 与普通队列不同,优先队列元素是按排序顺序检索的,但其元素可能没有排序。
- 假设我们想以升序检索元素。在这种情况下,优先队列的头是最小的元素。检索到该元素后,下一个最小的元素将成为队列的头。
创建PriorityQueue
// 导入包 import java.util.PriorityQueue; // 创建优先队列 PriorityQueue<Integer> numbers = new PriorityQueue<>();
在上述代码中,我们创建了一个没有任何参数的优先级队列。在这种情况下,优先级队列的头是队列中最小的元素,元素将按升序从队列中移除。但是,我们可以借助 Comparator 接口自定义元素的顺序。
将元素插入 PriorityQueue
add() - 将指定的元素插入队列。如果队列已满,则会引发异常。
offer() - 将指定的元素插入队列。如果队列已满,则返回false。
// 创建优先队列 PriorityQueue<Integer> numbers = new PriorityQueue<>(); // 使用add()方法 numbers.add(4); numbers.add(2); System.out.println("PriorityQueue: " + numbers); // 使用offer()方法 numbers.offer(1); System.out.println("更新后的PriorityQueue: " + numbers); // 输出结果 PriorityQueue: [2, 4] 更新后的PriorityQueue: [1, 4, 2]
访问 PriorityQueue 元素
- 要从优先级队列访问元素,我们可以使用peek()方法。此方法返回队列的头部。
// 创建优先级队列 PriorityQueue<Integer> numbers = new PriorityQueue<>(); numbers.add(4); numbers.add(2); numbers.add(1); System.out.println("PriorityQueue: " + numbers); // 使用peek()方法 int number = numbers.peek(); System.out.println("访问元素: " + number); // 输出结果 PriorityQueue: [1, 4, 2] 访问元素: 1
删除 PriorityQueue 元素
remove() - 从队列中删除指定的元素。
poll() - 返回并删除队列的开头。
// 创建优先队列 PriorityQueue<Integer> numbers = new PriorityQueue<>(); numbers.add(4); numbers.add(2); numbers.add(1); System.out.println("PriorityQueue: " + numbers); // 使用remove()方法 boolean result = numbers.remove(2); System.out.println("元素2是否已删除? " + result); // 使用poll()方法 int number = numbers.poll(); System.out.println("使用poll()删除的元素: " + number); // 输出结果 PriorityQueue: [1, 4, 2] 元素2是否已删除? true 使用poll()删除的元素: 1
遍历 PriorityQueue
- 可以使用 iterator() 方法,需要导入 java.util.Iterator 包。
// 创建优先级队列 PriorityQueue<Integer> numbers = new PriorityQueue<>(); numbers.add(4); numbers.add(2); numbers.add(1); System.out.print("使用iterator()遍历PriorityQueue : "); // 使用iterator()方法 Iterator<Integer> iterate = numbers.iterator(); while(iterate.hasNext()) { System.out.print(iterate.next()); System.out.print(", "); } // 输出结果 使用iterator()遍历PriorityQueue : 1, 4, 2,
PriorityQueue 其他方法
- contains(element) - 在优先级队列中搜索指定的元素。如果找到该元素,则返回true,否则返回false。
- size() - 返回优先级队列的长度。
- toArray() - 将优先级队列转换为数组,并返回它。
PriorityQueue 比较器 (comparator)
- 自定义元素检索顺序 - 我们需要创建自己的comparator类,它实现了Comparator接口。
import java.util.PriorityQueue; import java.util.Comparator; class Main { public static void main(String[] args) { // 创建优先级队列 PriorityQueue<Integer> numbers = new PriorityQueue<>(new CustomComparator()); numbers.add(4); numbers.add(2); numbers.add(1); numbers.add(3); System.out.print("PriorityQueue: " + numbers); } } class CustomComparator implements Comparator<Integer> { @Override public int compare(Integer number1, Integer number2) { int value = number1.compareTo(number2); // 元素以相反的顺序排序 if (value > 0) { return -1; } else if (value < 0) { return 1; } else { return 0; } } } // 输出结果 PriorityQueue: [4, 3, 1, 2]
思路 3:快速排序
- 首先选定一个基准数 base(一般选择 nums[0]),初始化两个指针 i 和 j,分别指向数组的上界和下界;
- 先从最右侧开始依次向左搜索,找到第一个小于 base 的数,再从最左侧位置开始依次向右搜索,找到第一个大于 base 的数,交换指针 i 和指针 j 所指向的元素的值;
- 重复以上操作直至指针 i 和 j 指向的元素相同,此时交换 base 与该元素的值;
- 分别递归处理数组中 base 左右两边的子数组。
- 时间复杂度 O(NlogN),空间复杂度 O(1)
public int findKthLargest(int[] nums, int k) {
int len = nums.length - 1;
quickSort(nums, 0, len);
return nums[len - k + 1];
}
public void quickSort(int[] nums, int low, int high) {
if (low > high) return;
int i = low, j = high;
// 基准数
int base = nums[low];
while (i < j) {
// 找到右边小于基准数的数字
while (nums[j] >= base && i < j) {
j--;
}
// 找到左边大于基准数的数字
while (nums[i] <= base && i < j) {
i++;
}
if (i < j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
nums[low] = nums[i];
nums[i] = base;
quickSort(nums, low, j - 1);
quickSort(nums, j + 1, high);
}
思路 4:快速选择排序
快速排序算法基于快速排序的思想衍生,其可以使某些需要 O(NlogN) 时间复杂度的问题,在平均复杂度 O(N) 下完成。常见的例子是求数组中的第 k 小的数。
- 首先选定一个基准数 base(一般选择 nums[0]),初始化两个指针 i 和 j,分别指向数组的上界和下界;
- 将数组中小于 base 的值移到数组左端,其他移动到数组右端;
- 计算 base 左端的数 (包括 base 自己) 有多少,记为 count;
- 如果 count 正好为 k,则返回此时的 base,此值即为第 k 小的数;
- 如果左端的数 count 大于 k,说明第 k 小的数在左端,因此只递归左边即可,同理,如果不在左端,只递归右边。
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
k = len - k;
int i = 0, j = len - 1;
while (i < j) {
int count = quickSelect(nums, i, j);
if (count == k) {
break;
} else if (count < k) {
i = count + 1;
} else {
j = count - 1;
}
}
return nums[k];
}
public int quickSelect(int[] nums, int low, int high) {
int i = low, j = high;
// 基准数
int base = nums[low];
while (i < j) {
// 找到右边小于基准数的数字
while (nums[j] >= base && i < j) {
j--;
}
// 找到左边大于基准数的数字
while (nums[i] <= base && i < j) {
i++;
}
if (i < j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
nums[low] = nums[i];
nums[i] = base;
return j;
}