【AcWing-Python-786】第k个数/快速选择算法

题目: https://www.acwing.com/problem/content/788/
对应视频讲解: https://www.acwing.com/video/228/

题目描述

【AcWing-Python-786】第k个数/快速选择算法_第1张图片

回顾快排

【AcWing-Python-785】快速排序 - CSDN博客

(一)步骤

  1. 找到分界点x:可以是区间最左端点、区间最右端点、或区间中点

  1. 将整个区间划分为两段,使得左边所有数Left ≤ x,右边所有数Right ≥ x【注意分界点位置的值不一定为x】

  1. 递归排序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:
            j -= 1
        
        if i j:
        quick_sort(arr, j+1, right)

quick_sort(nums, 0, n-1)
print(nums[k-1])

你可能感兴趣的:(LeetCode,算法,数据结构,排序算法)