给出了两个长度为 n 的排序数组,问题是在O( n )时间内找到它们的和数组的中位数,其中包含所有数组A的每个元素与数组B的每个元素之间可能成对的总和。
例如:设A [2,4,6]和B [1,3,5]是两个给定的数组。
sum数组是[2+1,2+3,2+5,4+1,4+3,4+5,6+1,6+3,6+5]
。在O( n )中找到该数组的中位数。
在O( n ^ 2 )中解决问题非常简单,但是对于这个问题是否有任何O( n )解决方案?
注意:这是向我的一位朋友询问的面试问题,面试官非常肯定可以在O( n )时间内解决。
答案 0 :(得分:14)
正确的O(n)解决方案非常复杂,需要大量的文本,代码和技巧来解释和证明。更准确地说,这需要3页才能令人信服,详见http://www.cse.yorku.ca/~andy/pubs/X+Y.pdf(simonzack
在评论中找到)。
它基本上是一个聪明的分而治之算法,除其他外,它利用了这样一个事实:在一个有序的n乘n矩阵中,人们可以在O(n)
中找到那些元素的数量。小于/大于给定数字k
。它递归地将矩阵分解为较小的子矩阵(仅采用奇数行和列,从而产生具有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)
算法来查找排序k
矩阵中小于/大于给定数字n-by-n
的数字量。请记住,我们不需要实际的矩阵!如OP所描述的,两个大小为n
的数组的笛卡尔和,得到一个排序的n-by-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)
[请注意,j
不会在for
循环]。 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 = candidate[i]
返回小于smaller_than_k(k)
的最大数字(n²+1)/2
是答案,并且是在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
}
答案 1 :(得分:1)
我们假设数组是A = {A[1] ... A[n]}
和B = {B[1] ... B[n]}
,成对和数组是C = {A[i] + B[j], where 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]
,这两项指标最为平衡&#34;。
然后问题减少到找到D
的中位数,其中只有n
元素。诸如Hoare's之类的算法将起作用。
更新:这个答案错了。这里真正的结论是中位数是D
的元素之一,但是 D
的中位数与C
的中位数不同。
答案 2 :(得分:0)
这不起作用吗?:
只要A
和B
已排序,您就可以在线性时间内计算数字的等级。您用于计算排名的技术也可用于查找A+B
中所有在时间上下限和上限之间的所有内容,与输出的大小相加|A|+|B|
。
从n
随机抽取A+B
件事。取中位数,比如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)的Universe中存在一般选择问题;你可以再做一次基本相同的技巧,找到一个与你选择的sqrt(n)log(n)成比例的大小范围。
原因如下:如果您从n组中采样k并获取中位数,则样本中位数的顺序介于(1/2 - sqrt(log(n)/ k))th和(1 / 2 + sqrt(log(n)/ k))具有至少恒定概率的元素。当n = | A + B |时,我们想要取k = sqrt(n),我们得到一个大约sqrt(n log n)个元素的范围---这是关于| A | log | A |。但是你再次这样做,你得到一个范围为sqrt(n)polylog(n)的范围。
答案 3 :(得分:0)
您应该使用选择算法在O(n)中查找未排序列表的中位数。看看这个:http://en.wikipedia.org/wiki/Selection_algorithm#Linear_general_selection_algorithm_-_Median_of_Medians_algorithm