例如:让A [2,4,6]和B [1,3,5]成为给定的两个数组。和数组是
[2+1,2+3,2+5,4+1,4+3,4+5,6+1,6+3,6+5]
。在O(n)中找到此数组的中位数。以O(n)方式解决问题相当直观,但是否存在O(n)的解决方案?
注意:这是面试中向我的一个朋友提出的问题,面试官非常确定可以在O(n)的时间内解决。
[2+1,2+3,2+5,4+1,4+3,4+5,6+1,6+3,6+5]
。在O(n)中找到此数组的中位数。正确的O(n)解决方案相当复杂,需要大量的文本、代码和技能来解释和证明。更确切地说,需要三页纸才能令人信服地说明这一点,详情请参见此处(由评论中的simonzack
发现)。
它基本上是一个聪明的分治算法,利用了排序的n×n矩阵中可以找到比给定数字k小/大的元素数量为O(n)的事实。它将矩阵递归地分解成较小的子矩阵(通过只取奇数行和列,得到一个有n/2列和n/2行的子矩阵),再与上述步骤结合起来,结果是复杂度为O(n)+O(n/2)+O(n/4)...=O(2*n)=O(n)。真的很不可思议!
我无法比论文更好地解释它,这就是为什么我会解释一个更简单的O(n logn)解决方案 :)
这是一次面试!你不能在时间内得到那个O(n)解决方案。所以嘿,为什么不提供一个解决方案,虽然不是最优的,但比其他明显的O(n²)候选方案更好呢?
我将利用上面提到的O(n)算法,在排序的n×n矩阵中找到比给定数字k小/大的元素数量。请记住,我们不需要一个实际的矩阵!如本文所述,两个大小为n的数组的笛卡尔和结果是一个排序的n×n矩阵,我们可以通过考虑数组的元素来模拟它:
a[3] = {1, 5, 9};
b[3] = {4, 6, 8};
//a + b:
{1+4, 1+6, 1+8,
5+4, 5+6, 5+8,
9+4, 9+6, 9+8}
因此,每行都包含非递减的数字,每列也是如此。现在,假设你有一个数字k
。我们想要在O(n)
的时间内找出在这个矩阵中比k
小和大的数字各有多少个。显然,如果这两个值都小于(n²+1)/2
,那么k
就是我们的中位数!
算法非常简单:
int smaller_than_k(int k){
int x = 0, j = n-1;
for(int i = 0; i < n; ++i){
while(j >= 0 && k <= a[i]+b[j]){
--j;
}
x += j+1;
}
return x;
}
这基本上是在每一行中计算符合条件的元素数量。由于行和列已经按照上面所示排序,这将提供正确的结果。由于i
和j
都最多迭代n
次,因此该算法的时间复杂度为O(n)
[请注意,在for
循环内j
没有被重置]。算法greater_than_k
类似。
那么我们该如何选择k
?这就是logn
部分。 二分查找!正如其他回答/评论中所提到的,中位数必须是包含在此数组中的值:
candidates[n] = {a[0]+b[n-1], a[1]+b[n-2],... a[n-1]+b[0]};
.
只需对此数组进行排序[也是O(n*logn)
],然后在其上运行二分查找。由于数组现在按非递减顺序排列,因此容易注意到小于每个candidate[i]
的数字数量也是一个非递减的值(单调函数),这使它适合于二分搜索。返回小于k
的结果小于(n²+1)/2
的最大数k = candidate[i]
是答案,并且可以在log(n)
次迭代中获得:
int b_search(){
int lo = 0, hi = n, mid, n2 = (n²+1)/2;
while(hi-lo > 1){
mid = (hi+lo)/2;
if(smaller_than_k(candidate[mid]) < n2)
lo = mid;
else
hi = mid;
}
return candidate[lo]; // the median
}
smaller_than_k(k)
,直到找到那个人。这样在最坏情况下会使其成为O(n ^ 2)
吧? - Khanh Nguyencandidates
是[14,23,32,41],但中位数是24和31的平均值。 - xanA = {A[1] ... A[n]}
和B = {B[1] ... B[n]}
,配对求和数组为C = {A[i] + B[j],其中1 <= i <= n,1 <= j <= n}
,它有n^2
个元素,我们需要找到它的中位数。
C
的中位数必须是数组D = {A[1] + B[n],A[2] + B[n - 1],... A[n] + B[1]}
中的一个元素:如果你固定A[i]
并考虑所有的和A[i] + B[j]
,你会发现唯一的A[i] + B[j = n + 1 - i]
(它是D
中的一个)可能是中位数。也就是说,它可能不是中位数,但如果不是,那么所有其他的A[i] + B[j]
也都不是中位数。B[j]
并计算比 A[i] + B[j]
小和大的值的数量(由于两个数组已排序,所以我们可以相当精确地进行计算——尽管计算有点混乱)。您会发现对于 A[i] + B[n + 1 - j]
,这两个计数最为“平衡”。D
的中位数,它只有 n
个元素。 Hoare's 这样的算法将起作用。
更新: 此答案是错误的。真正的结论是 中位数 是 D
的一个元素,但是 D
的中位数 不同于 C
的中位数。这个方法行不行?:
只要A
和B
是排序的,就可以在线性时间内计算一个数字的排名。你用于计算排名的技术也可以用于在时间线性输出大小加上|A|+|B|
的情况下找到A+B
中在某个下限和某个上限之间的所有内容。
从A+B
中随机抽取n
个元素。取中位数,称为foo
。计算foo
的排名。以恒定的概率,foo
的排名在中位数的排名上加减n
。重复此过程(预期次数为常数),直到您对中位数的下限和上限有了相差2n
的范围。(整个过程需要预期的线性时间,但显然很慢。)
现在,您只需要枚举边界之间的所有内容,并在线性大小的列表上进行线性时间选择即可。
(无关紧要的是,我不会因面试官问这样一个明显糟糕的面试问题而原谅他。像这样的东西根本不能说明您编码的能力。)
编辑:你可以通过以下方式计算一个数字x
的等级:
Set i = j = 0.
While j < |B| and A[i] + B[j] <= x, j++.
While i < |A| {
While A[i] + B[j] > x and j >= 0, j--.
If j < 0, break.
rank += j+1.
i++.
}
进一步编辑:实际上,上述技巧只能将A+B
的候选空间缩小到大约n log(n)个成员。然后,在大小为n log(n)的宇宙中,您可以进行一般的选择问题;您可以再次使用基本相同的技巧,并找到一个大小与sqrt(n) log(n)成比例的范围,在其中进行选择。
原因如下:如果从n集合中抽取k个物品并取中位数,则样本中位数的顺序在第(1/2-sqrt(log(n)/k))和第(1/2+sqrt(log(n)/k))个元素之间,至少具有常数概率。当n = |A+B|时,我们将希望取k = sqrt(n),我们得到大约sqrt(n log n)个元素的范围---大约是|A| log |A|。但是,然后您再次执行此操作,您将获得一个大约为sqrt(n) polylog(n)的顺序范围。
i
找到j
的下限和上限,使得范围内的所有元素都位于这些边界之间。然后,您可以枚举那些重要的A+B
元素。像这样的随机抽样技巧通常是击败二分搜索的关键。(作为奖励,它在实践中通常运行得更快。我也不相信它的实际用途,直到我看到有人真正使用了这样的技巧。) - tmyklebu你应该使用一种选择算法在O(n)时间内找到未排序列表的中位数。可以参考这个链接:http://en.wikipedia.org/wiki/Selection_algorithm#Linear_general_selection_algorithm_-_Median_of_Medians_algorithm