背景
解题
用归并排序
归并就是分解再合并的过程,主要涉及两个函数
一个是分解
一个是合并两个子数组
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
Solution.mergeSort(nums)
return nums[k-1]
def mergeSort(nums:List[int]):
n = len(nums)
# 剩一个或没有直接返回,不用排序
if n < 2:
return
# 拆分
mid = n // 2
s1 = nums[0:mid]
s2 = nums[mid:n]
# 子序列递归调用排序
Solution.mergeSort(s1)
Solution.mergeSort(s2)
# 合并
Solution.merge(s1,s2,nums)
def merge(s1,s2,s):
"""将两个列表是s1,s2按顺序融合为一个列表s,s为原列表"""
# j和i就相当于两个指向的位置,i指s1,j指s2
i = j = 0
while i+j<len(s):
# j==len(s2)时说明s2走完了,或者s1没走完并且s1中该位置是最小的
if j==len(s2) or (i<len(s1) and s1[i]>s2[j]):
s[i+j] = s1[i]
i += 1
else:
s[i+j] = s2[j]
j += 1
性能分析
这里还是引用王争的分析
我们假设对 n 个元素进行归并排序需要的时间是 T(n),那分解成两个子数组排序的时间都是 T(n/2)。
我们知道,merge() 函数合并两个有序子数组的时间复杂度是 O(n)。所以,套用前面的公式,归并排序的时间复杂度的计算公式就是:
T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。
T(n) = 2*T(n/2) + n; n>1
通过这个公式,如何来求解 T(n) 呢?还不够直观?那我们再进一步分解一下计算过程。
T(n)
= 2*T(n/2) + n = 2*(2*T(n/4) + n/2) + n
= 4*T(n/4) + 2*n
= 4*(2*T(n/8) + n/4) + 2*n
= 8*T(n/8) + 3*n = 8*(2*T(n/16) + n/8) + 3*n
= 16*T(n/16) + 4*n
......
= 2^k * T(n/2^k) + k * n
......
通过这样一步一步分解推导,我们可以得到 T(n) = 2^k * T(n/2^k)+kn。
中间那项 T(n/2^k)=T(1) 时,也就是 n/2^k=1,我们得到 k=log2n 。
我们将 k 值代入上面的公式,得到 T(n)=Cn+nlog2n 。
如果我们用大 O 标记法来表示的话,T(n) 就等于 O(nlogn)。
所以归并排序的时间复杂度是 O(nlogn)。从我们的原理分析和代码可以看出,归并排序的执行效率与要排序的原始数组的有序程度无关,所以其时间复杂度是非常稳定的,不管是最好情况、最坏情况,还是平均情况,时间复杂度都是 O(nlogn)
。
空间复杂度是 O(n)。
- 缺点
在比较合并的时候依然可以选择顺序,所以归并是稳定的排序