方法一 堆排序
此题其实用一个库函数sort就可以解决,但这显然不是本题的目的,所以我们要自己写一个算法来解决。
要找第k个大的元素,我们首先想到的就是堆排序。
正好我写过一个堆排序的文章,里面有详细的解释和代码
将堆排序的代码直接拿过来用就可以了。
Python代码
def findKthLargest(self, nums: List[int], k: int) -> int:
def headAdjust(k, l):
i = 2 * k + 1 # i为k的左子结点
temp = nums[k] # 暂存子树根结点
while i < l:
if i+1 < l and nums[i+1] > nums[i]: # 让i指向k的左右结点中大的那个
i += 1
if temp >= nums[i]: # 如果k的两个子节点都比k小
break
else:
nums[k] = nums[i]
k = i
i = 2*i + 1 # 指向被调整过的那棵子树
nums[k] = temp
def buildMaxHeap(l): # 建立大顶堆
for i in range(l//2-1, -1, -1):
headAdjust(i, l)
l = len(nums)
buildMaxHeap(l) # 建堆
for i in range(0, k-1):
nums[0], nums[l-i-1] = nums[l-i-1], nums[0] # 输出大顶堆中最大元素
headAdjust(0, l-i-1) # 调整堆
return nums[0]
时间复杂度O(nlogn)
空间复杂度O(logn)
buildMaxHeap
的循环里调用headAdjust()
算法时间复杂度为O(logn)
,而headAdjust()
里面使用了常数项变量temp
,所以空间复杂度为O(logn)
方法二 快速排序
思路
我们知道快速排序是一个递归的过程,每次都会找出下标为pivlotpos的元素,使该下标左侧的所有元素都比nums[pivlotpos]
小,右侧的都比它大。
我们可以利用这个特性,来找出第k大的元素。
只需对典型的快速排序算法做如下改动:
- 第k大的元素,也就是数组从大到小排序后下标为k-1的那一个元素,所以当
pivlotpos==k-1
的时候,我们就找到了第k大的元素,为了方便,我们可以先让k--
,这样到时候直接判断pivlotpos==k
- 排序默认是从小到大排,我们改成从大到小排
- 快排递归时默认先排左边子数组,再排右边子数组,我们改为条件判断。如果
pivlotpos > k
就排左侧子数组,反之排左侧子数组。
Python代码
def findKthLargest(self, nums: List[int], k: int) -> int:
def Partition(low, high):
pivlot = nums[low]
while low < high:
while low < high and nums[high] <= pivlot:
high -= 1
nums[low] = nums[high]
while low < high and nums[low] >= pivlot:
low += 1
nums[high] = nums[low]
nums[low] = pivlot
return low
def quickSort(low, high): # 对数组排序,找到第k大的数
if low < high:
pivlotpos = Partition(low, high)
if pivlotpos == k:
return None
elif pivlotpos > k:
quickSort(low, pivlotpos-1)
else:
quickSort(pivlotpos+1, high)
if nums == []:
return -1
low, high = 0, len(nums)-1
k -= 1 # 表示要寻找排序后下标为k的那个数
quickSort(low, high)
return nums[k]
时间复杂度O(n)
这里我也不知道怎么推出来的,证明过程参考「《算法导论》9.2:期望为线性的选择算法」。
空间复杂度O(logn)
递归所用的栈空间的代价