找到数组总和的中位数

时间:2013-06-26 09:51:12

标签: arrays algorithm median

给出了两个长度为 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 )时间内解决。

4 个答案:

答案 0 :(得分:14)

正确的O(n)解决方案非常复杂,需要大量的文本,代码和技巧来解释和证明。更准确地说,这需要3页才能令人信服,详见http://www.cse.yorku.ca/~andy/pubs/X+Y.pdfsimonzack在评论中找到)。

它基本上是一个聪明的分而治之算法,除其他外,它利用了这样一个事实:在一个有序的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 * 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;
}

这基本上计算了每行符合条件的元素数量。由于行和列已如上所示排序,因此将提供正确的结果。由于ij每次最多迭代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)

这不起作用吗?:

只要AB已排序,您就可以在线性时间内计算数字的等级。您用于计算排名的技术也可用于查找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