数组中的第k个最大元素
题目描述
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
思路
类似于取数组中前k大数的问题。
方法一:维护大小为k的最小堆,对于新元素,若小于堆顶则跳过,若大于等于堆顶则替换堆顶元素,重新调整堆,最后堆顶元素就是答案
方法二:使用快排思路,对于快排一次调整可以将一个元素调整到正确位置,因此可以二分调整。这里使用三路快排。
三路快排
https://www.cnblogs.com/deng-tao/p/6536302.html
三路快排思路是对于基准数k,做一次调整后将数组分为<k,=k,>k三部分。然后递归调整<k和>k的部分。
// 普通快排
func quickSort(nums []int, l, r int) {
if l >= r {
return
}
q, i, j := nums[l], l, r-1
for i < j {
// 从后往前找小于基准数的位置
for i < j && nums[j] >= q {
j--
}
// 从前往后找大于基准数的位置
for i < j && nums[i] <= q {
i++
}
// 交换小于和大于的元素,保证左边数组为小于基准数,右边数组大于基准数
if i < j {
nums[i], nums[j] = nums[j], nums[i]
}
}
// 将基准数调整到合适位置。即与小于数组的最后一位交换。
nums[l], nums[i] = nums[i], nums[l]
quickSort(nums, l, i)
quickSort(nums, i+1, r)
}
// 三路快排
func tripleQuickSort(nums []int, l, r int) {
if l >= r {
return
}
// 数组首先被划分为以下部分
// (l,lt] 小于基准数的部分
// (lt,i) 等于基准数的部分
// [i,gt) 待遍历元素
// [gt,r) 大于基准数的部分
// 起始状态,lt指向数组首元素,表示区间空,gr指向数组尾,表示区间空
// i 表示当前遍历元素的下标
q, lt, i, gt := nums[l], l, l+1, r
for i < gt {
// 元素小于基准数,则将i位置元素交换到小于区间,同时扩大小于区间的范围
if nums[i] < q {
nums[lt+1], nums[i] = nums[i], nums[lt+1]
lt++
i++
// 元素大于基准数,则将i位置元素交换到大于区间,同时扩大大于区间的范围
} else if nums[i] > q {
nums[gt-1], nums[i] = nums[i], nums[gt-1]
gt--
// 对于相等的元素,直接往下一位,表示扩大等于区间的范围
} else {
i++
}
}
// 将基准元素交换到小于空间的最后一位上
nums[lt], nums[l] = nums[l], nums[lt]
// 因此在一次调整之后,数组的区间应该变成:
// [l,lt) 小于基准数的部分
// [lt,gt) 等于基准数的部分
// [gt,r) 大于基准数的部分
tripleQuickSort(nums, l, lt)
tripleQuickSort(nums, gt, r)
}
补充一下:
对于遇到小于基准数的位置和大于基准数的位置,i是否自增的问题。
- 对于小于基准数的情况,首先一定有lt<i的关系
- 等于区间为空,那么此时满足lt+1=i,那么交换语句元素位置没有变化,因此扩大小于区间,即lt++,此时lt=i,i需要后移,否则死循环了
- 等于区间不为空,那么此时满足lt+1<i,那么交换语句相当于将等于区间的第一个元素与当前i位置元素交换,因此扩大小于区间,即lt++,此时i位置的元素变成了等于基准数的元素,所以直接i++
- 对于大于基准数的情况,由于是将gt-1位置上的元素与i位置元素交换,因此并不清楚gt-1位置上的元素是什么关系,因此i不能后移,需要进入下一次判断。
code
func findKthLargest(nums []int, k int) int {
var l, r, lt, gt int
l, r = 0, len(nums)
lt, gt = tripleSort(nums, l, r)
for true {
if len(nums)-k >= lt && len(nums)-k < gt {
return nums[lt]
}
if gt <= len(nums)-k {
l = gt
lt, gt = tripleSort(nums, l, r)
} else {
r = lt
lt, gt = tripleSort(nums, l, r)
}
}
return 0
}
func tripleSort(nums []int, l, r int) (int, int) {
if l >= r {
return -1, -1
}
q, lt, i, gt := nums[l], l, l+1, r
for i < gt {
if nums[i] < q {
nums[lt+1], nums[i] = nums[i], nums[lt+1]
lt++
i++
} else if nums[i] > q {
nums[gt-1], nums[i] = nums[i], nums[gt-1]
gt--
} else {
i++
}
}
nums[lt], nums[l] = nums[l], nums[lt]
return lt, gt
}