正确的 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 * logn) 解决方案:
是面试!你不能及时得到那个O(n)
解决方案。O(n²)
所以嘿,为什么不提供一个解决方案,虽然不是最佳的,但表明你可以比其他明显的候选人做得更好?
我将使用上面提到的算法来查找排序矩阵O(n)
中小于/大于给定数字的数字数量。请记住,我们不需要实际的矩阵!正如 OP 所描述的,两个 size 数组的笛卡尔和产生一个排序矩阵,我们可以通过考虑数组的元素来模拟它,如下所示:k
n-by-n
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]};
.
只需对该数组 [also 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
}