问题:
求一个数组中第 k 小的数。
要求:时间复杂度 严格O(N)
分析:
如果把数组进行排序,然后取出第 k 个数,那么复杂度为 O(NlogN)
这种方法不行。
那么下面给出两种解答方法,第二种就是今天讲的 BFPRT。
经典解法
- 利用快排 patition 划分
- 然后把比划分值小的放在左边,等于放中间,大于放右边,如果 k 在等于里面,那么命中了,如果没命中,重新划分,循环……
这种解法的缺点是什么呢,在于划分值得选取。所以 BFPRT 方法与经典解法的不同之处就在于划分值得选取。
- BFPRT
思路:
选择中位数组中的上中位数作为划分值。
具体实现:
- 假设整个实现为函数 int f(int[] arr, int k),返回值即为最后答案
- 先划分5个值为一组,每一组组内排序,每组内排序为常数级别,复杂度 O(1),有 N/5组,故,总的复杂度 O(N)
- 选择每一组中的上中位数,组成新的数组 newArr,数组长度为 N/5
- 调用自己,int m = f(newArr, N/10),即找到中位数组中的中位值m,这个值即为划分值。
下面分析为啥是这样的,看手写笔记吧:
长度为 N/5的 newArr 中,至少有 N/10的数比 m 小,
纵观所有数字,每5个一小组中,至少有两个数字比当初选择组成newArr 的那个值要小,那么这个数组中至少有3N/10个数字比 m 小,至多有7N/10个数字比 m 大。
同理,至少有3N/10个数字比 m 大,至多有7N/10个数字比 m 小。
也就是选择 m 作为划分值导致的结果是:比 m 大的和比 m 小的数都控制在3N/10~7N/10的范围内。
后续,要么对左边进行寻找,要么在右边进行寻找,那么就保证了,这种算法每次至少淘汰了3N/10个数字。
下面是潦草的笔记: