📅  最后修改于: 2023-12-03 15:36:16.029000             🧑  作者: Mango
在面试或算法竞赛中,有时需要从一个串联了多个数组的数组中找出第K个最小(或最大)的元素。本文介绍两种常见的方法来解决这个问题:快速选择和归并排序。
快速选择和快速排序类似,它们都是使用分治法思想,同时都是由 Tony Hoare 在20世纪60年代提出的。
在快速选择中,我们需要先选择一个元素作为 pivot,将小于 pivot 的元素放在左边,大于 pivot 的元素放在右边。然后,我们可以比较 pivot 的位置和 K 的大小,进一步递归左边或右边的子数组,直到找到第 K 个最小元素。
def kth_smallest(arr, left, right, k):
if k > 0 and k <= right - left + 1:
pos = partition(arr, left, right)
if pos - left == k - 1:
return arr[pos]
if pos - left > k - 1:
return kth_smallest(arr, left, pos - 1, k)
return kth_smallest(arr, pos + 1, right, k - pos + left - 1)
return None
def partition(arr, left, right):
pivot = arr[right]
i = left
for j in range(left, right):
if arr[j] <= pivot:
arr[i], arr[j] = arr[j], arr[i]
i += 1
arr[i], arr[right] = arr[right], arr[i]
return i
时间复杂度为 O(N),其中 N 为数组的长度。平均情况下,时间复杂度为 O(N)。
注意:快速选择算法是原址排序算法,因此它会改变原始数组的顺序。如果不希望改变原始数组的顺序,可以使用副本进行排序。
归并排序是一种稳定的排序算法,它使用分治法思想将数组分为两个子数组,然后递归地排序子数组,并将它们合并到一起。由于归并排序是稳定的,因此我们可以利用归并排序来解决这个问题。
我们可以将所有的数组按升序排序,然后用一个小根堆来维护每个数组当前最小的元素以及它所在的数组的编号。从小根堆中取出当前最小的元素,并更新最小元素所在的数组的指针,以便在下一次循环中从该数组中取出下一个最小元素。按照这种方式继续取出元素直到找到第 K 个最小元素。
import heapq
def kth_smallest(arr, k):
heap = []
for i, a in enumerate(arr):
if a:
heapq.heappush(heap, (a[0], i, 0))
for _ in range(k-1):
val, arr_idx, ele_idx = heapq.heappop(heap)
if ele_idx + 1 < len(arr[arr_idx]):
heapq.heappush(heap, (arr[arr_idx][ele_idx+1], arr_idx, ele_idx+1))
return heapq.heappop(heap)[0]
在归并排序中,我们需要最多合并 M 次,每次合并需要 O(N) 的时间复杂度,因此总时间复杂度为 O(MN logN)。由于我们在每次合并中只需要将最小的元素加入小根堆,因此堆的大小不会超过 M,所以总空间复杂度为 O(M)。
这篇文章介绍了两种方法来从串联M次的数组中找出第K个最小元素:快速选择和归并排序。快速选择的时间复杂度为 O(N),平均情况下时间复杂度为 O(N);归并排序的时间复杂度为 O(MN logN),空间复杂度为 O(M)。在实际应用中,根据输入数据的特点选择合适的算法和数据结构非常重要。