一、题目描述
在未排序的数组中找到第 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 ≤ 数组的长度。
二、思路分析
简单来说就是获取降序排序数组中下标为 k - 1 的值。
它这个第 k 个最大的元素不需要考虑数值重复的情况,直接排序后获取第 k 个值就可以了。
三、AC 代码
排序
var findKthLargest = function(nums, k) {
return nums.sort((a, b) => b - a)[k - 1]
/**
* const _nums = nums.sort((a, b) => b - a) // 降序排序
* return _nums[k - 1] // 返回第 k 个最大的元素。
* 因为数组是从 0 开始,所以要拿 k - 1 的那个元素
*/
}
最小堆
/**
* 最小堆
* 把值插入堆的底部,即数组的尾部。
* 然后上移,把这个值和它的父节点进行交换,直到父节点小于等于这个插入的值
* 大小为 k 的堆中,插入的元素的时间复杂度是 O(logk)
*/
class MinHeap {
constructor() {
this.heap = [] // 堆
}
/**
* 插入值
* @param value 要插入的值
*/
insert(value) {
this.heap.push(value) // 把值推入堆的尾部
this.shiftUp(this.heap.length - 1) // 执行上移方法
}
/**
* 上移
* @param index 要上移的值的下标
*/
shiftUp(index) {
if(index === 0) return // 边界判断
const heap = this.heap
const parentIndex = this.getParentIndex(index) // 获取上移值的父节点的下标
// 当父节点的值大于当前值,则进行上移,否则不做任何操作
if(heap[parentIndex] > this.heap[index]) {
this.swap(parentIndex, index) // 父子节点交换
this.shiftUp(parentIndex) // 继续向上移操作
}
}
/**
* 删除堆顶
* 由于 堆 的数据结构,所以我们不能直接删除堆顶的数据,需要把堆底的数据和堆顶的进行替换,然后进行下移操作
*/
pop() {
this.heap[0] = this.heap.pop() // 替换堆顶数据为堆底的
this.shiftDown(0) // 下移
}
/**
* 下移
* @param index 要下移的值的下标
*/
shiftDown(index) {
if(index >= this.length) return // 边界判断
const heap = this.heap
const leftIndex = this.getLeftIndex(index) // 获取左子节点
const rightIndex = this.getRightIndex(index) // 获取右子节点
// 当前节点值 大于 左子节点的值的时候
if(heap[index] > heap[leftIndex]) {
this.swap(index, leftIndex) // 交换
this.shiftDown(leftIndex) // 继续下移
}
// 当前节点值 大于 右子节点的值的时候
// 这里值得注意的是,当上述左子节点交换后,左子节点的值不一定就比右子节点小
// 所以哪怕上述方法通过且进行交换了,下述方法也要进行判断
if(heap[index] > heap[rightIndex]) {
this.swap(index, rightIndex) // 交换
this.shiftDown(rightIndex) // 继续下移
}
}
/**
* 交换值
* @param parentIndex 父节点下标
* @param index 子节点下标
*/
swap(parentIndex, index) {
const heap = this.heap
const temp = heap[parentIndex]
heap[parentIndex] = heap[index]
heap[index] = temp
}
/**
* 获取父元素的下标
* @param index 需要获取父元素下标元素的下标
*/
getParentIndex(index) {
// >> 右移操作符 就是把二进制数右移一位,等于除以二取商
return (index - 1) >> 1
// return Math.floor( (index - 1) / 2 ) 改代码和上述代码效果一致
}
/**
* 获取左子节点
* @param index 当前节点下标
*/
getLeftIndex(index) {
// 左移操作符 二进制位左移一位 相当于乘 2 | index * 2 + 1
return (index << 1) + 1
}
/**
* 获取右子节点
* @param index 当前节点下标
*/
getRightIndex(index) {
// 左移操作符 二进制位左移一位 相当于乘 2 | index * 2 + 2
return (index << 1) + 2
}
/**
* 获取堆顶
*/
peek() {
return this.heap[0]
}
/**
* 获取堆的长度
*/
size() {
return this.heap.length
}
}
// 获取第 k 个最大元素
// 该算法的思路是通过最小堆来设置堆顶为最小的值
// 然后限制当前堆的长度为 k 如果超出 k 个数,就把堆顶给删除掉
// 那么到最后堆顶自然就是第 k 大的元素了
var findKthLargest = function(nums, k) {
const h = new MinHeap()
nums.forEach(v => {
h.insert(v)
if(h.size() > k) {
h.pop()
}
})
return h.peek()
}
四、总结
其实主要问题就是排序,排序过后这个问题就迎刃而解了。
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情