在未排序的数组中找到第 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
我的方案
代码
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length-k];
}
}
官方方案 小顶堆
思想
- 堆是什么?
- 堆就是使用完全二叉树来维护的一个一维数组。(注意其本质是数组,也可以用二叉树的数据结构,但是考虑到这种数据结构相比数组而言,每个结点的大小增加了,而堆排序的算法却没有优化,还是平均N*log2(N)(N为结点数量))
- 本质上依然是数组,只是每次插入元素时,按照完全二叉树的特性来限制插入的位置(不断向上调整)
- 按照堆的特点可以把堆分为大顶堆和小顶堆
- 大顶堆:每个结点的值都大于或等于其左右孩子结点的值
小顶堆:每个结点的值都小于或等于其左右孩子结点的值- 因为这种特性,堆常常被当做优先队列使用,因为可以快速的访问到“最重要”的元素
- 堆数据结构的实现
- 可以使用Java内置的PriorityQueue,其底层实现正是基于数组的小顶堆,元素的插入都会保持堆的排序。
- 使用数组实现堆:
创建默认大小为11的数组,当有元素要加入堆时,调用函数offer(),加入数组中,当数组非空时,根据其与相应父结点的大小来siftUp调整位置。
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
//如果压入的元素为null 抛出异常
int i = size;
if (i >= queue.length)
grow(i + 1);
//如果数组的大小不够扩充
size = i + 1;
if (i == 0)
queue[0] = e;
//如果只有一个元素放在堆顶
else
siftUp(i, e);
//否则调用siftUp函数从下往上调整堆。
return true;
}
//在PriorityQueue的源代码中,当前元素与父节点不断比较如果比父节点小就交换然后继续向上比较,否则停止比较.
private void siftUp(int k) {
while (k > 0) {
//父节点位置:如果父亲节点是N,则两个孩子节点2*N+1,2(N+1),反推孩子节点编号k,则父亲节点为(k - 1) >>> 1
int parent = (k - 1) >>> 1;
//保存父节点的值
Object e = queue[parent];
//使用compareTo方法,如果要插入的元素小于父节点的位置则交换两个节点的位置
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
算法
创建一个优先队列,在迭代中维持队列的大小,超过K个就删除堆顶
代码
class Solution {
public int findKthLargest(int[] nums, int k) {
// init heap 'the smallest element first'
PriorityQueue<Integer> heap =
new PriorityQueue<Integer>();
//每次迭代保证堆中只存在K个元素,利用小顶堆的特性,大于k个元素,就将最小的值poll,迭代完毕后,留在堆顶的正是第K个最大元素。
for (int n: nums) {
heap.add(n);
if (heap.size() > k)
heap.poll();
}
// output
return heap.poll();
}
}