正确的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) . 太疯狂了!
我无法解释它比论文更好, which is why I'll explain a simpler, O(n logn) solution instead :) .
O(n * logn)解决方案:
It's an interview! 你无法及时得到那个 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;
}
这基本上计算了每行符合条件的元素数量 . 由于行和列已按上面所示排序,因此这将提供正确的结果 . 由于 i 和 j 每次最多迭代 n 次,算法为 O(n) [注意 j 不会在 for 循环内重置] . greater_than_k 算法类似 .
现在,我们如何选择 k ?那是 logn 部分 . Binary Search! 正如其他答案/评论中所提到的,中位数必须是此数组中包含的值:
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
}