LeetCode 4—寻找两个有序数组的中位数

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。 你可以假设 nums1 和 nums2 不会同时为空。

示例 1:

nums1 = [1, 3] nums2 = [2]

则中位数是 2.0

示例 2:

nums1 = [1, 2] nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5 

 

 

例如,对于[2 3 5 7],我们在3到5之间进行切割:

[2 3/5 7]

那么中位数=(3 + 5)/ 2。

请注意,我将使用'/'表示剪切,(数字/数字)表示通过本文中的数字进行剪切。

对于[2 3 4 5 6],

我们像这样直接通过:[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与R的索引:L =(N-1)/ 2,R为N / 2。 因此,中值可以表示为

(L + R)/2 = (A[(N-1)/2] + A[N/2])/2

为了准备好两个阵列的情况,让我们在数字之间添加一些虚构的“位置”(表示为#'),并将数字视为“位置”。

[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,索引(R)=(CutPosition))/ 2。

现在对于双阵列情况:

A1: [#  1  #  2 #  3 #  4 #  5 #]     (N1 = 5, N1_positions = 11)

 

A2: [#  1 #  1 #  1 #  1 #]          (N2 = 4, N2_positions = 9)

与单阵列问题类似,我们需要找到一个将两个阵列分成两半的切割,

使之满足“左半部分中的任何数字“<=”右边两个中的任何数字半”。

我们还可以做出以下观察:

1.总共有2N1 + 2N2 + 2个位置。 因此,在切口的每一侧必须有正好N1 + N2位置,并且在切口上直接有2个位置。

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  #]

3.切割时,我们有两个L's和两个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。现在我们可以使用简单的二进制搜索来找出结果。

如果我们有L1> R2,这意味着A1的左半部分有太多的大数字,那么我们必须向左移动C1(即向右移动C2);如果L2> R1,那么A2的左半部分有太多的大数字,我们必须向左移动C2。否则,这次切割是正确的。在找到切口后,切口可以计算为(max(L1,L2)+ min(R1,R2))/ 2;

两个旁注:

A.由于C1和C2可以相互确定,我们可以先移动其中一个,然后相应地计算另一个。但是,首先移动C2(较短阵列上的那个)更为实际。原因是在较短的阵列上,所有位置都可能是中位数的切割位置,但是在较长的阵列上,左侧或右侧太远的位置对于合法切割来说根本不可能。例如,[1],[2 3 4 5 6 7 8]。显然,2到3之间的切割是不可能的,因为较短的阵列没有那么多元素来平衡[3 4 5 6 7 8]部分,如果你这样切割。因此,对于要用作第一次切割的基础的较长阵列,必须执行范围检查。在较短的阵列上进行操作会更容易,不需要任何检查。此外,仅在较短的阵列上移动会产生O的运行时复杂度(log(min(N1,N2)))

B.唯一的边缘情况是切割落在第0(第一)或第2N(最后)位置。例如,如果C2 = 2N2,则R2 = A2 [2 * N2 / 2] = A2 [N2],其超过阵列的边界。为了解决这个问题,我们可以想象A1和A2实际上都有两个额外的元素,A_-的INT_MAX和A [N]的INT_MAX。这些添加不会改变结果,但会使实现更容易:如果任何L落在数组的左边界之外,则L = INT_MIN,如果任何R落在右边界之外,则R = INT_MAX。

代码实现:

public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        if (m < n) return findMedianSortedArrays(nums2, nums1);
        if (n == 0) return (nums1[(m - 1) / 2] + nums1[m / 2]) / 2.0;
        int left = 0, right = 2 * n;
        while (left <= right) {
            int mid2 = (left + right) / 2;
            int mid1 = m + n - mid2;
            double L1 = mid1 == 0 ? Double.MIN_VALUE : nums1[(mid1 - 1) / 2];
            double L2 = mid2 == 0 ? Double.MIN_VALUE : nums2[(mid2 - 1) / 2];
            double R1 = mid1 == m * 2 ? Double.MAX_VALUE : nums1[mid1 / 2];
            double R2 = mid2 == n * 2 ? Double.MAX_VALUE : nums2[mid2 / 2];
            if (L1 > R2) left = mid2 + 1;
            else if (L2 > R1) right = mid2 - 1;
            else return (Math.max(L1, L2) + Math.min(R1, R2)) / 2;
        }
        return -1;
    }
}

参考资料:链接一:LeetCode

                  链接二:LeetBook

你可能感兴趣的:(LeetCode)