在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
解法一:内置排序(Python)
时间复杂度: O ( N l o g N ) \mathcal O(NlogN) O(NlogN)
参考:
Python3 List sort()方法
python sort函数内部实现原理
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
nums.sort(reverse=True);
return nums[k-1]
解法二:最大堆(Python)
思路是创建一个大顶堆,将所有数组中的元素加入堆中,并保持堆的大小小于等于 k。这样,堆中就保留了前 k 个最大的元素。这样,堆顶的元素就是正确答案。
时间复杂度 :
O
(
N
log
k
)
\mathcal{O}(N \log k)
O(Nlogk)
空间复杂度 :
O
(
k
)
\mathcal{O}(k)
O(k),用于存储堆元素。
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
return heapq.nlargest(k, nums)[-1]
heapq.nlargest(n, iterable, key=None)
从 iterable 所定义的数据集中返回前 n 个最大元素组成的列表。 如果提供了 key 则其应指定一个单参数的函数,用于从 iterable 的每个元素中提取比较键 (例如 key=str.lower)。 等价于: sorted(iterable, key=key, reverse=True)[:n]
C++ priority_queue 实现
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int> > heap;
for(int num:nums)
{
heap.push(num);
if(heap.size()>k)
heap.pop();
}
int res = heap.top();
return res;
}
};
解法三:快速选择(Python)
快速选择算法 的平均时间复杂度为 O ( N ) \mathcal{O}(N) O(N)。就像快速排序那样,本算法也是 Tony Hoare 发明的,因此也被称为 Hoare选择算法。
本方法大致上与快速排序相同。简便起见,注意到第 k 个最大元素也就是第 N - k 个最小元素,因此可以用第 N - k 小算法来解决本问题。
首先,我们选择一个枢轴,并在线性时间内定义其在排序数组中的位置。这可以通过 划分算法(partition algorithm)的帮助来完成。
(为了实现划分,沿着数组移动,将每个元素与枢轴进行比较,并将小于枢轴的所有元素移动到枢轴的左侧)
这样,在输出的数组中,枢轴达到其合适位置。所有小于枢轴的元素都在其左侧,所有大于或等于的元素都在其右侧。
这样,数组就被分成了两部分。如果是快速排序算法,会在这里递归地对两部分进行快速排序,时间复杂度为 O ( N log N ) \mathcal{O}(N \log N) O(NlogN)
而在这里,由于知道要找的第 N - k 小的元素在哪部分中,我们不需要对两部分都做处理,这样就将平均时间复杂度下降到 O ( N ) \mathcal{O}(N) O(N)
最终的算法十分直接了当 :
-
随机选择一个枢轴
-
使用划分算法将枢轴放在数组中的合适位置 pos。将小于枢轴的元素移到左边,大于等于枢轴的元素移到右边
-
比较 pos 和 N - k 以决定在哪边继续递归处理
时间复杂度 : 平均情况
O
(
N
)
\mathcal{O}(N)
O(N),最坏情况
O
(
N
2
)
\mathcal{O}(N^2)
O(N2)
空间复杂度 :
O
(
1
)
\mathcal{O}(1)
O(1)
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
def partition(left, right, pivot_index):
pivot = nums[pivot_index]
# move pivot to end
nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
# move all smaller elements to the left
store_index = left
for i in range(left, right):
if nums[i] < pivot:
nums[store_index], nums[i] = nums[i], nums[store_index]
store_index += 1
# move pivot to its final place
nums[right], nums[store_index] = nums[store_index], nums[right]
return store_index
def select(left, right, k_smallest):
if left == right: return nums[left] # if the list only have one element
# select a random pivot_index
pivot_index = random.randint(left, right)
# find the pivot position
pivot_index = partition(left, right, pivot_index)
# the pivot is in its final sorted position
if k_smallest == pivot_index:
return nums[pivot_index]
# go left
elif k_smallest < pivot_index:
return select(left, pivot_index - 1, k_smallest)
else:
return select(pivot_index+1, right, k_smallest)
return select(0, len(nums) - 1, len(nums)-k)
partition algorithm 演示