数组中的第K个最大元素 - 力扣(LeetCode)
在未排序的数组中找到第 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就可以。示例2还是蛮奇怪的,首先,他这个数据是带重的; 其次,他这个同样的数,是加k的
快排法
快排核心思想就是分治和分区,partition,每次用piviot把数据分成两(三)部分。
我们选择数组区间 A[0…n-1] 的最后一个元素 A[n-1] 作为 pivot,对数组 A[0…n-1] 原地分区,这样数组就分成了三部分,A[0…p-1]、A[p]、A[p+1…n-1]。
如果 p+1=K,那 A[p] 就是要求解的元素;如果 K>p+1, 说明第 K 大元素出现在 A[p+1…n-1] 区间,我们再按照上面的思路递归地在 A[p+1…n-1] 这个区间内查找。同理,如果 K<p+1,那我们就在 A[0…p-1] 区间查找。
“如果 K>p+1, 说明第 K 大元素出现在 A[p+1…n-1] 区间” 这里其实不太对,因为有重复数据的出现,还要考虑等于的情况。
话说为什么每次Pivot都选最后一个数?是不是对于随机的数据,选每个数都一样的?
下图是从小往大排,作为示例理解一下可以,不过k大也差不多,就是倒过来,从大往小排。

算法复杂度O(n)
每一次都是找到一个标定点,然后将这个标定点挪到数组中合适的位置,注意这个所谓的合适的位置,恰恰是这个数组在排好序后,最终所处的位置。比如说在这个例子中,如果我们把4挪到的这个位置,这个位置,相应的也是数组中的第4个位置,那么整个数组的第4名就是4。第4名以前的元素都在4的前面,第4名以后的元素都在4的后面。
如果问,这个数组第6名是什么呢,我们在做完这一次partition之后就完全不用管,前面的部分了,因为我们知道我们的标定点在第4位,那么第6名的位置一定在这个第4位的后面的部分。我们只需要继续的递归的去求解,后面的这部分的第2名是谁就行了,如果我问你的事儿,这个数组的第2位是谁呢,上面,我们把4方到这个位置之后,我们只需要继续求解,前面这一部分的第2名是谁就好了,那么整体的思路就是这个样子。
class Solution:
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
def partition(left, right, pivot_index):
pivot = nums[pivot_index]
# 1. move pivot to end
nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
# 2. move all smaller elements to the left
store_index = left
for i in range(left, right):
if nums[i] < pivot:
nums[store_index], nums[i] = nums[i], nums[store_index]
store_index += 1
# 3. move pivot to its final place
nums[right], nums[store_index] = nums[store_index], nums[right]
return store_index
def select(left, right, k_smallest):
"""
Returns the k-th smallest element of list within left..right
"""
if left == right: # If the list contains only one element,
return nums[left] # return that element
# select a random pivot_index between
pivot_index = random.randint(left, right)
# find the pivot position in a sorted list
pivot_index = partition(left, right, pivot_index)
# the pivot is in its final sorted position
if k_smallest == pivot_index:
return nums[k_smallest]
# go left
elif k_smallest < pivot_index:
return select(left, pivot_index - 1, k_smallest)
# go right
else:
return select(pivot_index + 1, right, k_smallest)
# kth largest is (n - k)th smallest
return select(0, len(nums) - 1, len(nums) - k)
为什么上述解决思路的时间复杂度是 O(n)?
第一次分区查找,我们需要对大小为 n 的数组执行分区操作,需要遍历 n 个元素。第二次分区查找,我们只需要对大小为 n/2 的数组执行分区操作,需要遍历 n/2 个元素。依次类推,分区遍历元素的个数分别为、n/2、n/4、n/8、n/16.……直到区间缩小为 1。
如果我们把每次分区遍历的元素个数加起来,就是:n+n/2+n/4+n/8+…+1。这是一个等比数列求和,最后的和等于 2n-1。所以,上述解决思路的时间复杂度就为 O(n)。
如果,每次取数组中的最小值,将其移动到数组的最前面,然后在剩下的数组中继续找最小值,以此类推,执行 K 次,找到的数据不就是第 K 大元素了吗?
不过,时间复杂度就并不是 O(n) 了,而是 O(K n)。时间复杂度前面的系数不是可以忽略吗?O(K n) 不就等于 O(n) 吗?
这个可不能这么简单地划等号。当 K 是比较小的常量时,比如 1、2,那最好时间复杂度确实是 O(n);但当 K 等于 n/2 或者 n 时,这种最坏情况下的时间复杂度就是 O(n2) 了。
还有优先队列的写法,学不明白,以后再更
参考文章:极客时间 王争 算法与数据结构之美
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。