1.1. 题目
1.1.1. 数组中的第K个最大元素
在未排序的数组中找到第 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 ≤ 数组的长度。
1.1.2. 题解:
- 数组无序,找出第K个最大的元素,意思就是把数组排序,从大到小,找到从大到小第k个元素是啥
- 假如第k个元素和第k-1个元素是一样的,不能合并成一个.. 比如 5,4,3,3,3,2 里第4大元素就是 3,并不是2
1.1.3. 思路:
- 看到题的第一思路是 用个数组做个有序栈,首先是个栈,其次是有序的,栈的大小是k
- 遍历数组,依次把每个元素都添加到栈里,如果超出栈,那么栈最小那个推出去,并把新的值有序插入到栈里
- 当遍历完所有的元素之后,栈最后一个元素就是第K个最大的元素
- 每次入栈都得排序一次,因为栈里都是有序的,所以栈里排序时间复杂度是 O(n),外围遍历元素时间复杂度是 O(n),因为是套着的
- 所以时间复杂度是 O(n^2)
- 看官方标签写的是堆排序
- 我特地去了解了下堆排序,堆排序平均复杂度是O(n log n),那优先选堆排序
- 花了半天,终于看懂且写下了堆排序了.
- 堆排序主要思想是先把数组隐射到 二叉树 上(不需要代码实现),然后把数组重新排序,达到 父节点一定要比自己的子节点都大或者都小
- 其实堆排序好理解,但是不太好写,最主要的是不太好写 创建堆 的代码...
- 第一, 数组长度 / 2 的节点很可能都是非叶子节点,如果不是 得 判断当前节点的子节点有没有超出长度.. 超出了说明它没有子节点.. 也就不用换了...
- 第二, 每个 父节点和自己的子节点一旦 发生了换位,那么可能它子节点的子节点可能也要变化,所以需要注意..
- 第三, 每排完一次,就把根节点 和堆的最后一个节点替换(不等于数组的最后一个节点)
- 堆排序的顺序是
- 先构建堆
- 数组长度的/2之后的值肯定都是叶子节点,无需当做父节点进行构建,所以往前遍历
- 如果当前节点的
左右子节点
比数组总长度
小 且 比父节点小(或者大,看你要怎么排序),那么进行替换 - 替换后 子节点和父节点进行变换了,但是以前的子节点 可能还有子节点,可能子节点的子节点已经不满足最大或者最小堆了,所以得往下轮查变换一遍
- 构建成功后,此时根节点(数组首位)就是最大或者最小值,这时候把当前值与
堆
的最后一个值进行替换,注意是堆的最后一个值 - 替换后,把堆最后这个值剔除堆外,剩下的值重新上面的构建堆的过程,注意这时候上面的
数组长度
其实是堆的长度了.. - 当把所有元素都遍历完了,那么最后的数组就是一个有序的数组了
- 先构建堆
1.1.4. 代码:
点击显示
func findKthLargest(nums []int, k int) int {
nums = heapSort(nums)
return nums[k-1]
}
func heapSort(nums []int) []int {
lens := len(nums)
// 先建立一个堆
buildHeap(nums,lens)
// 上面构建堆后,其实已经满足了基本的 最大堆或者最小堆
// 此时根节点肯定是最大值或者最小值了
// 从数组最后挨个往前遍历
for i:=lens-1;i>=0;i-- {
// 构成堆后的数组的第一个值和最后一个值进行替换
// 然后把 数组 i 个值之后的数之外的数 继续构建堆
// 循环往复
swap(nums,0,i)
lens -= 1
heap(nums,0,lens)
}
return nums
}
// 构建一个堆
func buildHeap(nums []int,lens int) {
// 一般数组长度的一半 很可能就不是叶子节点了,但可以确定的是 数组长度一半后面的肯定都是叶子节点
// 叶子节点是最底下的节点了,所以不需要往下置换了,所以从 长度/2开始算
for i := lens/2;i>=0;i-- {
heap(nums,i,lens)
}
}
func heap(nums []int,i,lens int) {
// 每个父节点的左节点 是当前数组下标i的 2*i + 1
// 每个父节点的右节点 是当前数组下标i的 2*i + 2
left := 2*i + 1
right := 2*i + 2
// 咱们做最小堆,也就是小的数组排在后面
// 假设当前的 父节点是最小
min := i
// 当 父节点的左子节点 小于 数组总长度 且 左子节点的值小于父节点的时候,所以这个小堆里 左子节点是最小的(暂时不替换)
// 为什么要 父节点的左子节点 小于 数组总长度, 因为如果父节点的左子节点比 数组总长度还长,那说明 超出了数组范围了..说明该父节点其实没有左子节点
if left < lens && nums[left] < nums[min] {
min = left
}
// 右子节点跟上面左子节点一个道理
if right < lens && nums[right] < nums[min] {
min = right
}
// 如果通过上面与左右子节点相比,最小值已经不是当初的父节点了
// 那么把最小的节点与父节点进行变换,变换后可能会影响该父节点的子节点的子节点,所以还得往下继续对比比换一下
if min != i {
swap(nums,min,i)
heap(nums,min,lens)
}
}
func swap(arr []int,m,n int) []int {
arr[m],arr[n] = arr[n],arr[m]
return arr
}