📅  最后修改于: 2023-12-03 14:56:55.232000             🧑  作者: Mango
给定一个包含 $n$ 个正整数的数组 $arr$,以及正整数 $k$。请设计一个函数找出 $arr$ 中第 $k$ 个最大的奇数(如果不存在第 $k$ 个最大的奇数,则返回 $-1$)。
我们可以先将数组 $arr$ 中的所有奇数取出,并进行降序排序。然后直接返回排序后第 $k$ 个奇数即可。但是该方法时间复杂度为 $O(nlogn)$,不够高效。
代码实现:
def findKth(arr: List[int], k: int) -> int:
odds = [x for x in arr if x % 2 == 1]
odds.sort(reverse=True)
return -1 if k > len(odds) else odds[k - 1]
我们可以使用一个小根堆来维护 $arr$ 中所有奇数,当堆的大小小于等于 $k$ 时,我们把所有奇数依次加入堆中。当堆的大小超过 $k$ 时,我们就将堆顶元素(即最小的奇数)弹出,直到堆的大小等于 $k$。此时堆顶元素即为题目所求。
代码实现:
import heapq
def findKth(arr: List[int], k: int) -> int:
odds = [-x for x in arr if x % 2 == 1]
heapq.heapify(odds)
while len(odds) > k:
heapq.heappop(odds)
return -1 if len(odds) < k else -odds[0]
我们可以借助快速排序算法来解决。在快速排序的过程中,我们每次会选择一个数(例如pivot)将数组分割成两个部分,其中左半部分的数都小于等于pivot,右半部分的数都大于等于pivot。这个过程会产生一个整数 $m$,表示pivot所在的位置。
如果 $m$ 恰好等于 $k$,则pivot就是我们要找的答案。如果 $m$ 大于等于 $k$,说明第 $k$ 个最大的奇数在左半部分中,我们可以递归地处理左半部分;如果 $m$ 小于 $k$,说明第 $k$ 个最大的奇数在右半部分中,我们可以递归地处理右半部分。
代码实现:
import random
def quick_select(nums, k):
def partition(left, right, pivot):
nums[pivot], nums[right] = nums[right], nums[pivot]
j = left - 1
for i in range(left, right):
if nums[i] >= nums[right]:
j += 1
nums[i], nums[j] = nums[j], nums[i]
nums[right], nums[j + 1] = nums[j + 1], nums[right]
return j + 1
left, right = 0, len(nums) - 1
while left <= right:
pivot = random.randint(left, right)
m = partition(left, right, pivot)
if m == k:
return nums[m]
elif m > k:
right = m - 1
else:
left = m + 1
return -1
def findKth(arr: List[int], k: int) -> int:
odds = [x for x in arr if x % 2 == 1]
if len(odds) < k:
return -1
return quick_select(odds, k - 1)
在本题中我们介绍了三种解法:排序、堆以及快速选择算法。其中排序算法最容易想到,但是时间复杂度较高;堆算法常数较小,但是仍然需要 $O(nlogk)$的时间复杂度;快速选择算法效率最高,具有 $O(n)$ 的时间复杂度。