求数组总和的中位数

Posted

技术标签:

【中文标题】求数组总和的中位数【英文标题】:Find the median of the sum of the arrays 【发布时间】:2013-06-23 09:38:32 【问题描述】:

给定两个长度为 n 的排序数组,问题是在 O(n) 时间内找到它们和数组的中位数,其中包含所有数组 A 的每个元素和数组 B 的每个元素之间可能的成对和。

例如:让 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^2) 中解决问题非常简单,但是有任何 O(n) 解决方案来解决这个问题吗?

注意:这是向我的一位朋友提出的面试问题,面试官非常确定可以在 O(n) 时间内解决。

【问题讨论】:

你知道求和的中位数是不是中位数的和吗? 嘿,OP 声明数组的总和更像笛卡尔积,结果数组包含N*N 元素。请注意。 呃。这绝对是可能的(Mirzaian–Arjomandi 1985),但在面试中期望 O(n) 算法是愚蠢的。 @user814628 这是 O(n^2) 而不是 O(n) 这里是 Mirzaian–Arjomandi 1985 的链接,正如 David 所说:cse.yorku.ca/~andy/pubs/X+Y.pdf 【参考方案1】:

正确的 O(n) 解决方案相当复杂,需要大量的文本、代码和技巧来解释和证明。更准确地说,它需要 3 页才能令人信服地做到这一点,详情请参见 http://www.cse.yorku.ca/~andy/pubs/X+Y.pdf(在 cmets 中由 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 * logn) 解:

这是一次采访!您无法及时得到O(n) 的解决方案。所以,嘿,为什么不提供一个解决方案,虽然不是最佳的,但表明你可以比其他明显的O(n²) 候选人做得更好?

我将利用上面提到的O(n) 算法,在排序的n-by-n 矩阵中找到小于/大于给定数字k 的数字数量。请记住,我们不需要实际的矩阵!正如 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 部分。 二分搜索! 正如其他答案/cmets 中提到的,中位数必须是此数组中包含的值:

candidates[n] = a[0]+b[n-1], a[1]+b[n-2],... a[n-1]+b[0];.

简单地排序这个数组[也是O(n*logn)],然后对其进行二分搜索。由于数组现在处于非递减顺序,因此可以直接注意到小于每个 candidate[i] 的数字数量也是非递减值(单调函数),这使其适用于二分搜索。结果smaller_than_k(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

【讨论】:

"而且 i 和 j 都最多迭代 n 次,所以算法是 O(n)" => 不应该是 O(n^2) 吗? @KhanhNguyen j 不依赖于 i。它从n-1 开始,总共最多减去n 次(它不会重置为n-1)。所以最多有2*n 个迭代组合。 但还有另一个问题:如果我是对的,在对候选人进行排序后,您对 每个 候选人运行smaller_than_k(k),直到找到那个候选人。在最坏的情况下,这不是O(n^2)吗? 您能否详细解释一下为什么答案在candidates 之中?其他答案只给出了一个想法,但我无法给出彻底的证明。 中位数不一定位于矩阵的对角线上(给定的candidates 矩阵),正如@Mikhail 所想的那样。考虑[1,2,3,4][10,20,30,40]candidates[14,23,32,41] 但中位数是 24 和 31 的平均值。【参考方案2】:

假设数组是A = A[1] ... A[n]B = B[1] ... B[n],成对和数组是C = A[i] + B[j], where 1 &lt;= i &lt;= n, 1 &lt;= j &lt;= n,其中有n^2 元素,我们需要找到它的中位数。

C 的中值必须是数组D = A[1] + B[n], A[2] + B[n - 1], ... A[n] + B[1] 的一个元素:如果你修复A[i],并考虑所有的和A[i] + B[j],你会看到只有 @987654330 @(这是D 之一)可能是中位数。也就是说,它可能不是中位数,但如果不是,那么所有其他A[i] + B[j] 也不是中位数。

这可以通过考虑所有B[j] 来证明,并计算 较低 的值 strong>大于 比A[i] + B[j] (我们可以非常准确地做到这一点,因为这两个数组是排序的——计算有点混乱)。您会看到,对于 A[i] + B[n + 1 - j],这两个计数是最“平衡的”。

然后问题归结为找到D 的中位数,它只有n 元素。诸如Hoare's 之类的算法将起作用。

更新:这个答案是错误的。这里真正的结论是 medianD 的元素之一,但是 D 的中值C' 不一样中位数。

【讨论】:

这是 aaronman 说的,不是吗?我以为有反例? 如果您无法阅读已删除的帖子,请考虑 [0 1 1 1 2] 和 [0 0 0 1 2]。如果我对您的理解正确,您的“对角线”是 [2 2 1 1 2],其中位数是 2。但正确的结果是 1。 Somone 在论文中找到了解决方案,但如果它可以用 c++ 或 java 的代码交付,或者至少用比论文中更少的数学术语解释,那就太好了 @aaronman 你(或我)不必在错误时删除你的答案。没有规则说你不能发布错误的答案,只要你投入足够的时间和精力。只需投反对票,为以后的观众留下便条。我们所要做的就是提供一个好的答案。我的回答是错误的,但这是一个想法。把它留在这里,未来的观众不会犯同样的错误(并希望通过改进得到答案)。而且,如果你没有删除你的帖子,我就不会浪费时间尝试同样的想法! 如果你知道答案是错误的,你应该删除它。【参考方案3】:

这不行吗?:

只要对AB 进行排序,您就可以在线性时间内计算数字的排名。您用于计算排名的技术也可用于查找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))th 个元素,至少具有恒定概率。当 n = |A+B| 时,我们将取 k = sqrt(n) 并得到大约 sqrt(n log n) 元素的范围 --- 这大约是 |A|记录 |A|。但是你再做一次,你会得到一个 sqrt(n) polylog(n) 顺序的范围。

【讨论】:

所以排名高于线性(嵌套for循环)解决方案不是线性的 任何说“随机”的东西通常都具有无穷大的最坏情况复杂性。 不,排名计算显然是线性的。这就是所谓的“拉斯维加斯”算法;它总是返回正确的答案,并且它的预期运行时间很好。 All you have to do now is enumerate everything between the bounds and do a linear-time selection on a linear-sized list. 你打算如何计算这个列表?请记住,数字不需要很小,您的 2n 数字列表可能有 10^7 的下限和 10^9 的上限,您需要弄清楚其中的 2n 个数字是什么。除此之外,您的解决方案与我的解决方案有点相似,只是我使用二进制搜索而不是随机算法。 @Arthur:您计算该列表就像计算排名一样。在j 上为每个i 找到下限和上限,以便范围内的所有内容都位于边界之间。然后你可以列举A+B 的那几个元素。像这样的随机抽样技巧通常是击败二分搜索的关键。 (作为奖励,它通常在实践中运行得更快。在我看到有人实际使用这样的技巧之前,我也不相信它的实际用途。)【参考方案4】:

您应该使用选择算法在 O(n) 中找到未排序列表的中位数。看这个:http://en.wikipedia.org/wiki/Selection_algorithm#Linear_general_selection_algorithm_-_Median_of_Medians_algorithm

【讨论】:

以上是关于求数组总和的中位数的主要内容,如果未能解决你的问题,请参考以下文章

求两个数组的中位数

求指定整数数组的中位数

LeetCode题目----求中位数---标签:Array

求中位数,O(n)的java实现利用快速排序折半查找中位数

算法作业!!求两个不等长有序数组的中位数!

怎么在O(N)时间内求一个无序数组的中位数