目录
以下摘自leetcode Top100精选题目--堆篇
数组中的第K个最大元素
给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例:
输入: [3,2,1,5,6,4], k = 2
输出: 5
Solution:
class Solution {
// 主函数,接收一个整数数组和一个整数作为参数
public int findKthLargest(int[] nums, int k) {
// 使用快速选择算法找到第k个最大的元素
// 注意,我们需要找到的是第k个最大的元素,但是在快速选择算法中,我们寻找的是第k个最小的元素
// 所以,我们需要寻找的是第nums.length - k个最小的元素
return select(nums, 0, nums.length - 1, nums.length - k);
}
// 分区函数,用于将数组分为两部分
private int partition(int[] nums, int left, int right) {
// 生成一个随机数,用于选择枢轴
Random rand = new Random();
int pivotIndex = left + rand.nextInt(right - left + 1);
// 将枢轴的值保存下来
int pivotValue = nums[pivotIndex];
// 将枢轴移动到数组的末尾
swap(nums, pivotIndex, right);
// storeIndex用于记录小于枢轴的元素的数量
int storeIndex = left;
// 遍历数组
for (int i = left; i < right; i++) {
// 如果当前的元素小于枢轴
if (nums[i] < pivotValue) {
// 将当前的元素和storeIndex位置的元素交换
swap(nums, i, storeIndex);
// storeIndex向右移动一位
storeIndex++;
}
}
// 将枢轴移动到正确的位置
swap(nums, storeIndex, right);
// 返回storeIndex,这就是枢轴的正确位置
return storeIndex;
}
// 选择函数,用于找到第k个最小的元素
private int select(int[] nums, int left, int right, int kSmallest) {
// 如果left和right相等,那么数组中只有一个元素,这就是我们要找的元素
if (left == right) {
return nums[left];
}
// 使用分区函数找到枢轴的正确位置
int pivotIndex = partition(nums, left, right);
// 如果枢轴的正确位置就是我们要找的位置
if (kSmallest == pivotIndex) {
// 那么枢轴的值就是我们要找的元素
return nums[kSmallest];
}
// 如果枢轴的正确位置在我们要找的位置的右边
else if (kSmallest < pivotIndex) {
// 那么我们要找的元素在枢轴的左边
return select(nums, left, pivotIndex - 1, kSmallest);
}
// 如果枢轴的正确位置在我们要找的位置的左边
else {
// 那么我们要找的元素在枢轴的右边
return select(nums, pivotIndex + 1, right, kSmallest);
}
}
// 交换函数,用于交换数组中的两个元素
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
先定义partition
函数,将数组分为两部分,左边的部分都比右边的部分小。定义一个select
函数,递归地在数组的一部分中寻找第k个最小的元素。定义swap
函数,交换数组中的两个元素。
在findKthLargest
函数中,调用select
函数,寻找的是第k个最大的元素,需要寻找的是第nums.length - k
个最小的元素。
前 K 个高频元素
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
示例:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
Solution:
class Solution {
// 主函数,接收一个整数数组和一个整数k作为参数
public int[] topKFrequent(int[] nums, int k) {
// 创建一个哈希表,用于存储每个元素出现的次数
Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
for (int num : nums) {
occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
}
// 创建一个优先队列,用于存储元素及其出现的次数
// 优先队列的大小为k,因此它始终只保留出现次数最多的k个元素
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
// 定义比较器,使得优先队列可以按照元素出现的次数进行排序
public int compare(int[] m, int[] n) {
return m[1] - n[1];
}
});
// 遍历哈希表中的每个元素及其出现的次数
for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
// 如果优先队列的大小等于k
if (queue.size() == k) {
// 检查队列顶部的元素的出现次数是否小于当前元素的出现次数
if (queue.peek()[1] < count) {
// 如果是,那么移除队列顶部的元素,并将当前元素添加到队列中
queue.poll();
queue.offer(new int[]{num, count});
}
} else {
// 如果优先队列的大小小于k,那么直接将当前元素添加到队列中
queue.offer(new int[]{num, count});
}
}
// 创建一个整数数组,用于存储结果
int[] ret = new int[k];
// 遍历优先队列,将队列中的元素添加到结果数组中
for (int i = 0; i < k; ++i) {
ret[i] = queue.poll()[0];
}
// 返回结果数组
return ret;
}
}
数据流的中位数
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
arr = [2,3,4]
的中位数是3
。 - 例如
arr = [2,3]
的中位数是(2 + 3) / 2 = 2.5
。
实现 MedianFinder 类:
-
MedianFinder()
初始化MedianFinder
对象。 -
void addNum(int num)
将数据流中的整数num
添加到数据结构中。 -
double findMedian()
返回到目前为止所有元素的中位数。与实际答案相差10-5
以内的答案将被接受。
示例:
输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]
解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0
Solution:
import java.util.PriorityQueue;
class MedianFinder {
// 创建两个堆,一个最大堆用于存储较小的一半数,一个最小堆用于存储较大的一半数
PriorityQueue<Integer> small; // max heap
PriorityQueue<Integer> large; // min heap
/** 初始化数据结构 */
public MedianFinder() {
small = new PriorityQueue<>((a, b) -> b - a); // 使用lambda表达式创建一个最大堆
large = new PriorityQueue<>(); // 创建一个最小堆
}
/** 将数据流中的整数 num 添加到数据结构中 */
public void addNum(int num) {
// 如果small堆为空,或者num小于等于small堆的顶部元素,将num添加到small堆中
if (small.isEmpty() || num <= small.peek()) {
small.offer(num);
}
// 否则,将num添加到large堆中
else {
large.offer(num);
}
// 平衡两个堆,确保small堆的大小不超过large堆的大小加1,且large堆的大小不超过small堆的大小
if (small.size() > large.size() + 1) {
large.offer(small.poll());
} else if (large.size() > small.size()) {
small.offer(large.poll());
}
}
/** 返回到目前为止所有元素的中位数 */
public double findMedian() {
// 如果small堆的大小大于large堆的大小,返回small堆的顶部元素
if (small.size() > large.size()) {
return small.peek();
}
// 否则,返回small堆的顶部元素和large堆的顶部元素的平均值
else {
return (small.peek() + large.peek()) / 2.0;
}
}
}