LeetCode 215. 数组中的第K个最大元素

251 阅读1分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

215. 数组中的第K个最大元素

难度中等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]
}