这个问题是众所周知的难以实现由于所有的角落情况。大多数实现将奇数和偶数数组视为两种不同的情况,并分别对待它们。事实上,有一点想法扭曲。这两种情况可以组合为一个,导致一个非常简单的解决方案,几乎不需要特殊的处理。
首先,让我们以一种非常规的方式看待“中值”的概念。那是:
“ 如果我们将排序数组切割为两半的EQUAL LENGTHS,则
中位数是Max(lower_half)和Min(upper_half)的AVERAGE,即
紧接着切割的两个数字。
例如,对于[2 3 5 7],我们在3和5之间切割:
[2 3 / 5 7]
那么中值=(3 + 5)/ 2。请注意,我将使用“/”表示剪切,并使用(数字/数字)来表示通过本文中的数字进行的剪切。
对于[2 3 4 5 6],我们使切割通过4像这样:
[2 3(4/4)5 7]
由于我们将4分成了两半,我们现在说下层和上层子阵列包含4。这个概念也导致正确的答案:(4 + 4)/ 2 = 4;
为了方便起见,我们使用L表示紧接着切口的数字,R表示右边对应的数字。例如,在[2 3 5 7]中,我们分别有L = 3和R = 5。
我们观察到L和R的索引与数组N的长度有以下关系:
N Index of L / R
1 0 / 0
2 0 / 1
3 1 / 1
4 1 / 2
5 2 / 2
6 2 / 3
7 3 / 3
8 3 / 4
不难得出L =(N-1)/ 2,R是N / 2的结论。因此,中值可以表示为
(L + R)/2 = (A[(N-1)/2] + A[N/2])/2
为了准备好两个数组的情况,让我们在数字之间添加一些虚数的“positions”(表示为#),并将数字视为“positions”。
[6 9 13 18] -> [# 6 # 9 # 13 # 18 #] (N = 4)
position index 0 1 2 3 4 5 6 7 8 (N_Position = 9)
[6 9 11 13 18]-> [# 6 # 9 # 11 # 13 # 18 #] (N = 5)
position index 0 1 2 3 4 5 6 7 8 9 10 (N_Position = 11)
可以看出,无论长度N如何,总是有2 * N + 1'个位置。因此,中间切割应该始终在第N个位置(基于0)。由于在这种情况下索引(L)=(N-1)/ 2和索引(R)= N / 2,我们可以推断索引(L)=(CutPosition-1)/ 2,索引)/ 2。
现在对于双阵列情况:
A1: [# 1 # 2 # 3 # 4 # 5 #] (N1 = 5, N1_positions = 11)
A2: [# 1 # 1 # 1 # 1 #] (N2 = 4, N2_positions = 9)
与单数组问题类似,我们需要找到一个将两个数组分成两半的剪切
“两个左半部分中的任何数字”<=“两个右半
部分中的任何数字”。
我们还可以提出以下意见:
-
有2个N1 + 2 N2 + 2位置。因此,在切口的每一侧必须有精确的N1 + N2位置,并且在切口的正上方必须有2个位置。
-
因此,当我们在A2中的位置C2 = K处切割时,A1中的切割位置必须为C1 = N1 + N2-k。例如,如果C2 = 2,则我们必须具有C1 = 4 + 5-C2 = 7。
[# 1 # 2 # 3 # (4/4) # 5 #] [# 1 / 1 # 1 # 1 #]
-
当切割时,我们有两个L'和两个R。他们是
L1 = A1[(C1-1)/2]; R1 = A1[C1/2]; L2 = A2[(C2-1)/2]; R2 = A2[C2/2];
在上述示例中,
L1 = A1[(7-1)/2] = A1[3] = 4; R1 = A1[7/2] = A1[3] = 4;
L2 = A2[(2-1)/2] = A2[0] = 1; R2 = A1[2/2] = A1[1] = 1;
现在我们如何决定这个切口是否是我们想要的切口?因为L1,L2是左半边上最大的数字,R1,R2是右边最小的数字,我们只需要
L1 <= R1 && L1 <= R2 && L2 <= R1 && L2 <= R2
以确保下半部分中的任何数字<=上半部分中的任意数字。事实上,由于
L1 <= R1和L2 <= R2是自然保证的,因为A1和A2被排序,我们只需要确保:
L1 <= R2和L2 <= R1。
现在我们可以使用简单的二分搜索找出结果。
If we have L1 > R1, it means there are too many large numbers on the left half of A1, then we must move C1 to the left (i.e. move C2 to the right);
If L2 > R1, then there are too many large numbers on the left half of A2, and we must move C2 to the left.
Otherwise, this cut is the right one.
After we find the cut, the medium can be computed as (max(L1, L2) + min(R1, R2)) / 2;
两个侧面说明:
A.由于C1和C2可以彼此相互确定,我们可以选择较短的数组(例如A2),并且只移动C2,并相应地计算C1。这样我们可以实现O(log(min(N1,N2))的运行时复杂度
B.唯一的边缘情况是切割落在第0(第一)或第2 N(最后)位置。例如,如果C2 = 2N2,则R2 = A2 [2 * N2 / 2] = A2 [N2],其超过阵列的边界。为了解决这个问题,我们可以想象A1和A2实际上都有两个额外的元素,INT_MAX在A [-1],INT_MAX在A [N]。这些添加不改变结果,但是使实现更容易:如果任何L落在阵列的左边界之外,则L = INT_MIN,并且如果任何R落在右边界外,则R = INT_MAX。
我知道这不是很容易理解,但所有上述推理最终归结为以下简洁的代码:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int N1 = nums1.size();
int N2 = nums2.size();
if (N1 < N2) return findMedianSortedArrays(nums2, nums1); // Make sure A2 is the shorter one.
if (N2 == 0) return ((double)nums1[(N1-1)/2] + (double)nums1[N1/2])/2; // If A2 is empty
int lo = 0, hi = N2 * 2;
while (lo <= hi) {
int mid2 = (lo + hi) / 2; // Try Cut 2
int mid1 = N1 + N2 - mid2; // Calculate Cut 1 accordingly
double L1 = (mid1 == 0) ? INT_MIN : nums1[(mid1-1)/2]; // Get L1, R1, L2, R2 respectively
double L2 = (mid2 == 0) ? INT_MIN : nums2[(mid2-1)/2];
double R1 = (mid1 == N1 * 2) ? INT_MAX : nums1[(mid1)/2];
double R2 = (mid2 == N2 * 2) ? INT_MAX : nums2[(mid2)/2];
if (L1 > R2) lo = mid2 + 1; // A1's lower half is too big; need to move C1 left (C2 right)
else if (L2 > R1) hi = mid2 - 1; // A2's lower half too big; need to move C2 left.
else return (max(L1,L2) + min(R1, R2)) / 2; // Otherwise, that's the right cut.
}
return -1;
}