题目: https://www.acwing.com/problem/content/788/
对应视频讲解: https://www.acwing.com/video/228/
题目描述
回顾快排
【AcWing-Python-785】快速排序 - CSDN博客
(一)步骤
找到分界点x:可以是区间最左端点、区间最右端点、或区间中点
将整个区间划分为两段,使得左边所有数Left ≤ x,右边所有数Right ≥ x【注意分界点位置的值不一定为x】
递归排序Left,递归排序Right
(二)时间复杂度
排序的时间复杂度是O(n * logn),快速排序的时间复杂度是O(n)
(三)与快选的联系
经过步骤1和2后,整个区间里,在分界点左边的数都≤x(假设长度为SL),右边的数都≥x(假设长度为SR)。现在想找到第k大的数
若 k ≤ SL,则整个区间第k小的数一定在左边,此时只需递归处理左边即可
若 k >SL,则整个区间第k小的数一定在右边,此时只需递归处理右边即可
快排需要递归左右两边,而快选只需要递归一边即可
快选的时间复杂度为O(n)
先对整个区间划分两部分,使左边都≤x,右边都≥x,这步需要扫描整个区间n,故为O(n)
根据第k小的数的k,与左边区间的长度SL的大小关系,判断是递归处理左边还是右边,只需扫描一半的区间即可,故为O(n/2)
以此类推,n + n/2 + n/4 + n/8 + ... = n (1 + 1/2 + 1/4 + 1/8 + ...) ≤ 2n,故为O(2n),因为常数不影响,故还是O(n)
解题思路及代码
(一)思路
首先明确,第k大的数X在有序数组中的位置为k-1(i ∈ [0, n-1])
采用快排分治思想,每次可只处理X所在的那一半区间,所以在进行快排找分界点时,找到此次递归的数组分界点j,将此次递归区间 [l, r] 分为 [l, j] 和 [j+1, r] 左右两个区间,并确定左右区间中的数,这些数在有序数组中也处于该区间,这样就可以根据j来确定k-1在左区间还是在右区间
如果 k - 1 <= j,说明k - 1在左区间,则递归处理左区间,舍去(不处理)右区间
反之如果k - 1 > j,说明k - 1在右区间,则递归处理右区间,舍去(不处理)左区间
不断递归,区间长度为1时,则找到了在有序数组中下标为k-1的数X
(二)代码
n, k = map(int, input().split())
nums = list(map(int, input().split()))
def quick_sort(arr, left, right):
if left >= right:
return
i, j, x = left-1, right+1, arr[(left+right)//2]
while i < j:
i += 1
j -= 1
while arr[i]<x:
i += 1
while arr[j]>x:
j -= 1
if i<j:
arr[i], arr[j] = arr[j], arr[i]
# 此时j左边都是≤x的数,j右边都是≥x的数
# 要找到排序后第k小的数,也即下标为 k-1 的数
if k-1 <= j:
quick_sort(arr, left, j)
if k-1 > j:
quick_sort(arr, j+1, right)
quick_sort(nums, 0, n-1)
print(nums[k-1])