Python 第k个最小元素(K’th Smallest Element)

目录

【朴素方法】使用排序——时间复杂度为 O(n log(n)),空间复杂度为 O(1)

【预期方法】使用最大堆 - 时间复杂度为 O(n * log(k)),空间复杂度为 O(k)

【替代方案 1】使用快速选择

【替代方案 2】使用计数排序


如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

给定一个整数数组arr[]和元素个数k,求数组中第 k 小的元素。
注意: k 始终小于数组的大小。

例如:

输入:arr[] = [10, 5, 4, 3, 48, 6, 2, 33, 53, 10], k = 4

输出:5

说明:给定数组中第四小的元素是 5。

输入:arr[] = [7, 10, 4, 3, 20, 15], k = 3

输出:7

说明:给定数组中第三小的元素是 7。

【朴素方法】使用排序——时间复杂度为 O(n log(n)),空间复杂度为 O(1)

其思路是对给定的数组进行排序,并返回索引 k - 1 处的元素。

def kthSmallest(arr, k):
    
    # Sort the given vector
    arr.sort()

    # Return k'th element in the sorted vector
    return arr[k - 1]


if __name__ == "__main__":
    arr = [10, 5, 4, 3, 48, 6, 2, 33, 53, 10]
    k = 4

    print(kthSmallest(arr, k))

输出
5

【预期方法】使用最大堆 - 时间复杂度为 O(n * log(k)),空间复杂度为 O(k)

其思路是在遍历数组的过程中维护一个大小为 k 的最大堆。该堆始终包含目前为止遇到的 k 个最小元素。如果堆的大小超过 k,则移除最大的元素。最终,堆中只保留 k 个最小元素。

import heapq

def kthSmallest(arr, k):
    
    # Create a max heap 
    pq = []

    # Iterate through the array elements
    for i in range(len(arr)):
        
        # Push the current element onto the max heap
        heapq.heappush(pq, -arr[i])  

        # If the size of the max heap exceeds k,
        #remove the largest element
        if len(pq) > k:
            heapq.heappop(pq)

    return -pq[0]

if __name__ == '__main__':
    arr = [10, 5, 4, 3, 48, 6, 2, 33, 53, 10]
    k = 4
    
    print(kthSmallest(arr, k))

输出
5

【替代方案 1】使用快速选择

主要思路是利用快速选择(QuickSelect)函数找到第 k 大元素。具体做法是:选择一个基准元素,然后将数组分割成多个部分,使得大于基准元素的元素位于左侧,小于基准元素的元素位于右侧。如果基准元素最终位于索引 k-1 处,则该元素即为第 k 大元素。否则,我们递归地仅在包含第 k 大元素的左侧或右侧部分进行搜索。

def partition(arr, left, right):
    
    # Choose the last element as pivot
    pivot = arr[right]
    i = left

    # Traverse the array and move elements <= pivot to the left
    for j in range(left, right):
        if arr[j] <= pivot:
            
            # Swap current element with element at i
            arr[i], arr[j] = arr[j], arr[i]
            i += 1

    # Place the pivot in its correct position
    arr[i], arr[right] = arr[right], arr[i]
    return i

# QuickSelect function: recursively finds k-th smallest
def quickSelect(arr, left, right, k):

    if left <= right:
        
        # Partition around pivot
        pivotIndex = partition(arr, left, right)
        
        # Found k-th smallest
        if pivotIndex == k:
            return arr[pivotIndex]
        
        elif pivotIndex > k:
            return quickSelect(arr, left, pivotIndex - 1, k)
        
        else:
            return quickSelect(arr, pivotIndex + 1, right, k)
    return -1

def kthSmallest(arr, k):
    return quickSelect(arr, 0, len(arr) - 1, k - 1)

if __name__ == "__main__":
    arr = [10, 5, 4, 3, 48, 6, 2, 33, 53, 10]
    k = 4
    print(kthSmallest(arr, k))

输出
5

时间复杂度: 最坏情况下为O(n² ),但平均时间为 O(n log n),且性能优于基于优先级队列的算法。

辅助空间: 最坏情况下递归调用栈为 O(n)。平均而言:O(log n)。

【替代方案 2】使用计数排序

主要思路是利用计数排序的频率计数来跟踪有多少元素小于或等于每个值,然后直接从这些累积计数中识别出第 K 小的元素,而无需对数组进行完全排序。

注意:这种方法在元素范围较小时特别有效,因为我们声明的数组大小为最大元素个数。如果元素范围非常大,计数排序方法可能并非最有效的选择。

def kthSmallest(arr, k):
 
    # First, find the maximum element in the list
    maxElement = arr[0]
    for i in range(1, len(arr)):
        if arr[i] > maxElement:
            maxElement = arr[i]

    # Create a frequency array for each element
    freq = [0] * (maxElement + 1)
    for i in range(len(arr)):
        freq[arr[i]] += 1

    # Keep track of cumulative frequency to find k-th smallest
    count = 0
    for i in range(maxElement + 1):
        if freq[i] != 0:
            count += freq[i]
            if count >= k:
                
                # If we have seen k or more elements,
                # return the current element
                return i
    return -1

if __name__ == "__main__":
    arr = [10, 5, 4, 3, 48, 6, 2, 33, 53, 10]
    k = 4
    print(kthSmallest(arr, k))

输出
5

时间复杂度: O(n + maxElement),其中 maxElement 为数组中的最大元素。

辅助空间: O(maxElement)。

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hefeng_aspnet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值