本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
难度中等1823
给定整数数组
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 <= 105-104 <= nums[i] <= 104
思路:
- 我们也可以使用堆排序来解决这个问题——建立一个大根堆,做 k - 1 次删除操作后堆顶元素就是我们要找的答案。在很多语言中,都有优先队列或者堆的的容器可以直接使用,但是在面试中,面试官更倾向于让更面试者自己实现一个堆。所以建议读者掌握这里大根堆的实现方法,在这道题中尤其要搞懂「建堆」、「调整」和「删除」的过程。
- 友情提醒:「堆排」在很多大公司的面试中都很常见,不了解的同学建议参考《算法导论》或者大家的数据结构教材,一定要学会这个知识点哦!^_^
时间复杂度: O(nlogn),建堆的时间代价是 O(n),删除的总代价是 O(k * logn),因为 k < n,故渐进时间复杂为 O(n + k * log n) = O(n log n)。
空间复杂度: O(logn),即递归使用栈空间的空间代价
func findKthLargest(nums []int, k int) int {
return HeapSort(nums, k)
}
// 堆排(大顶堆:每个节点的值都大于或等于其左右孩子节点的值)
func HeapSort(arr []int, k int) int {
var CreateHeap = func(arr []int, i, length int) {
father := arr[i]
// 注意for循环条件:是 j<length 而不是 j<len(arr)
for j := 2*i + 1; j < length; j = j*2 + 1 { // j=2i+1:当前根节点的左孩子下标 j= 2*j + 1:以当前叶子节点为新根节点,该新根节点的下一层叶子节点左孩子下标
if j+1 < length && arr[j] < arr[j+1] { // j+1<length:右孩子(j+1)不能超出len长度范围
j++
}
if father > arr[j] { // 左右孩子节点中选较大的节点值,并与父节点比较大小
break // 若父节点值满足"大于或等于其左右孩子节点的值"则break,否则与较大的孩子节点相互交换
}
arr[i] = arr[j]
i = j
}
arr[i] = father // 将最终比较后较小值放到合适的位置
}
// 首次构建堆
l := len(arr)
for i := l / 2; i >= 0; i-- { // 从二叉树最后一个父节点从底向上遍历(最上面的父节点:i = 0;最后一个父节点下标:i = len(arr) / 2)
CreateHeap(arr, i, l)
}
// 再次重建堆
for i := l - 1; i >= l-k+1; i-- { // 从下往上不断在每轮循环中置换出当前最大值,arr长度i也逐渐减到倒数第k大的下标位置
arr[0], arr[i] = arr[i], arr[0] // swap 把大顶堆根节点(下标为0)上的最大值交换到末尾,置换出来.
CreateHeap(arr, 0, i)
}
return arr[0]
}