Top-k 问题
Top k 问题是一个经典的算法问题,要求求解数组中第 k 大(or 第k小的元素)。最简单的,该问题可以通过排序来解决,算法的时间复杂度为 O(nlogn)。有没有效率更高的算法呢?
实际上,我们可以使用类似快排的思想,来降低时间复杂度。这里,我们使用 leetcode 中的题目作为例子(便于测试)215. 数组中的第K个最大元素。具体做法是:
- 在数组区间
[0,len)
中随机选择一个 pivot 值,然后遍历数组,如果数值大于 pivot 的值,则将这个数放置到数组的前半部分,否则放置到数组的后半部分。 - 完成第一步后,我们得到有
x
个元素大于 pivot 值,与 k 进行比较,如下三种情况:- 情况1:如果
x == k-1
, 则说明 pivot 为第 k 个最大的元素,直接返回 pivot。 - 情况2:如果
x < k-1
, 则说明 第k大的元素大于等于 pivot 值,调整区间 左边界left
等于 pivot 的下标加1,右边界不动; - 情况3:如果
x > k-1
, 则说明 第k大的元素小于 pivot 值,调整区间的右边界right
等于 pivot 的下标。 - 重复循环,分析
[left, right)
区间的情况。
- 情况1:如果
实现代码如下:
1 | class Solution { |
上面的代码中,需要考虑一个特殊的情况,即如果 kth 元素在一个重复数字的区间段内,是否能够正确的返回。通过分析,发现是可以的,这种特殊的情况与 分析中的情况2 是一致,即遍历到最后时,会有大于 枢值的元素个数为 0,即 x == 0
,这时候,枢轴元素会被移动到最左边,即 left 指向的位置。最后,左区间会基于枢轴位置向右调整1格,即到 left + 1。因此,不会进入死循环,cur 会不断移动,直到等于 k-1 为止。
对于上述的算法,它的实际时间复杂度是与 pivot 值的选择有关的,在最坏的情况下,时间复杂度可能达到 O(n^2)。但通过 随机选择枢轴,从概率的角度来说,该算法的平均时间复杂度为 O(n)。具体的数学证明在这里省略了,我们可以通过 主定理 粗略的进行分析(因为主定理要求子问题规模相同,而这里并不满足,所以是说是粗略分析):
- 子问题的个数a 为 1个,因为我们将区间划分为两块,而最后只需要分析其中一块即可;
- 输入数据规模虽小的因子b 为2,假设枢轴恰好位于数组中间;
- 除递归操作额外所需要的操作耗时为 O(n), 即 d = 1。
因此 $a<b^d$, 算法的时间复杂度为 O(n^d) = O(n)。
类似这个类型的题目还有 347. 前 K 个高频元素。对于寻找前 k 个高频元素,首先需要将各个元素出现的次数统计到一个以元素值为 key,出现次数为 value 的 map 中。然后通过上述 方法查到前 k 个元素(前k个元素之间不需要按顺序排列!)。
一些阅读材料: