【LeetCode】4. Median of Two Sorted Arrays

1 篇文章 0 订阅
1 篇文章 0 订阅

题目描述

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

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

The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5

解题思路

我们首先想到的解法可能是,将这两个有序数组先整合为一个有序数组,因为两个数组原本就是有序的,我们可以使用归并排序合并两个数组,然后找到它的中位数,算法复杂度为:O(m+n)。但是要把时间复杂度降到O(log(m+n)),就需要对两个数组同时做二分查找,排除掉不可能出现中位数的区间,最后找到所求的中位数。

第一种方法

预备知识

为了解决这个问题,我们首先要知道什么是中位数?在统计中,中位数用于:

将一个集合划分为两个相等长度的子集,其中一个子集总是大于另一个子集。

首先我们先以数组A为例,随机的在位置i把数组A分割成两部分:

      left_A             |        right_A
A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]

因为 A 有 m 个元素,所以总共有 m+1 种切割位置(i = 0 ~ m)

我们知道:

len(left_A) = i, len(right_A) = m - i
Note: 当 i = 0,left_A为空;当i = m, right_A为空

对数组B做同样的操作,在随机位置 j 处将数组B切割为两个部分:

      left_B             |        right_B
B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]

我们将left_Aleft_B放入一个集合中,将right_Aright_B 放入一个集合,分别命名这个两个集合为:left_partright_part

      left_part          |        right_part
A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]
B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]

如果我们可以满足下面两个条件:

1. len(left_part) = len(right_part)
2. max(left_part) ≤ min(right_part)

那么,我们便将集合 A 和集合 B 中所有的元素分成了相同长度的两部分,并且其中一部分总是大于另一部分,所以:

=max(left_part)+min(rightpart)2 中 位 数 = m a x ( l e f t _ − p a r t ) + m i n ( r i g h t − p a r t ) 2

为了确保上面两个条件,我们只需满足:

  • i+j=mi+nj i + j = m − i + n − j

    如果 nm n ≥ m 我们仅需要使 i = 0 ~ m, j=m+n+12i j = m + n + 1 2 − i

  • B[j1]A[i] B [ j − 1 ] ≤ A [ i ] A[i1]B[j] A [ i − 1 ] ≤ B [ j ] ji 关系为: j=m+n+12i j = m + n + 1 2 − i

备注:

  1. 为了简单,我们假设 A[i−1],B[j−1],A[i],B[j] 都是有效的,即时 i = 0、i = m、 j = 0、j = n,后面会讲解对于如何处理这些边缘值。
  2. 因为我们要确保 j 为非负值 (j0 ( j ≥ 0 ), j=m+n+12i j = m + n + 1 2 − i ,所以当 mn m ≤ n 可以保证 j0 j ≥ 0 。(其实就是让第一个数组A的长度要不能大于第二个数组B的长度)

所以我们需要做的是:

[0,m]中搜索找到一个 i 使得满足下面条件:

B[j1]A[i]A[i1]B[j],j=m+n+12i B [ j − 1 ] ≤ A [ i ] 和 A [ i − 1 ] ≤ B [ j ] , j = m + n + 1 2 − i


算法流程

我们可以按照以下步骤进行二分法搜索

  1. 设置两个变量imin = 0、imax = m,然后开始在[imin , imax]中搜索
  2. 设置变量 i=imin+imax2 i = i m i n + i m a x 2 , j=m+n+12i j = m + n + 1 2 − i
  3. 现在我们已经有了长度相同的两部分:len(left_part) = len(right_part),而我们可能遇到的只有以下三种情况:
    * B[j1]A[i] B [ j − 1 ] ≤ A [ i ] A[i1]B[j] A [ i − 1 ] ≤ B [ j ]

    意味着我们已经找到了这个位置 i,这样就可以得到max(left_part)、min(right_part),所以我们就可以得到所求的中位数,就可以停止搜索。

    • B[j1]>A[i] B [ j − 1 ] > A [ i ]

      这表明A[i]太小了,我们必须调整i使满足 B[j1]A[i] B [ j − 1 ] ≤ A [ i ]

      那么我们如何调整 i,是增大 i 的值还是降低 i 的值?答案是增加i的值,因为我们知道数组是有序的,那么增加 i 的值,则根据 j=m+n+12i j = m + n + 1 2 − i ,j 就会下降,则 i 变大,A[i]变大,B[j-1]变小。这样就有可能满足 B[j1]A[i] B [ j − 1 ] ≥ A [ i ]

      我们知道我么需要增加 i 的值使其满足条件,那么我们必须调整搜索范围为 [i+1, imax],所以使 imain = i+1,然后跳到第 2 步骤。

    • A[i1]>B[j] A [ i − 1 ] > B [ j ]

      这表示 A[i-1] 太大了,我们需要降低 i 的值,这样才能满足 A[i1]B[j] A [ i − 1 ] ≤ B [ j ] 。所以我们需要调整搜索范围为: [imin, i-1],所以设置 imax = i-1。然后跳到第 2 步骤。

当我们按照以上三步骤,找到所需的 i 时,这个中位数就是:

* 当 m+n 的和为奇数时,中位数=max(A[i-1], B[j-1])
* 当 m+n 的和为偶数时,中位数=( max(A[i−1],B[j−1])+min(A[i],B[j]) )/ 2

边界值问题

现在我们考虑边界值问题:i=0、j=0、i=m、j=n时,A[i−1],B[j−1],A[i],B[j]可能不存在,其实这种情况更简单。

现在我们知道要确保 max(left_partmin(rightpart) m a x ( l e f t _ − p a r t ≤ m i n ( r i g h t − p a r t ) ,所以当 i 和 j 不是上面的边界值时,意味着A[i−1],B[j−1],A[i],B[j]都存在,此时我们需要检查 B[j1]A[i] B [ j − 1 ] ≤ A [ i ] A[i1]B[j] A [ i − 1 ] ≤ B [ j ] 。但是如果A[i−1],B[j−1],A[i],B[j]中有一些不存在的话,那么我们可能不需要检查 B[j1]A[i] B [ j − 1 ] ≤ A [ i ] A[i1]B[j] A [ i − 1 ] ≤ B [ j ] 一个或者全部。

例如,i = 0时,A[i-1]是不存在的,那么我们不需要检查 (A[i1]B[j] ( A [ i − 1 ] ≤ B [ j ] 。所以我们需要做的是:

  • 在[0,m]中搜索找到一个 i ,使得:
    • (j = 0 or i = m or B[j1]A[i] B [ j − 1 ] ≤ A [ i ] ) and (i = 0 or j = n or A[i1]B[j] A [ i − 1 ] ≤ B [ j ] ), j=m+n+12i j = m + n + 1 2 − i

算法总结

在搜索循环中,我们只会遇到三种情况:

  1. (j = 0 or i = m or B[j1]A[i] B [ j − 1 ] ≤ A [ i ] ) and (i = 0 or j = n or A[i1]B[j] A [ i − 1 ] ≤ B [ j ] )。这种情况意味着我们找到的 i 是正好的,此时停止搜索。
  2. j > 0 and i < m and B[j−1] > A[i]。意味着 i 太小,我们需要增大它
  3. i > 0 and j < n and A[i−1] > B[j]。意味着 i 太大,我们需要降低它

注:因为 i < m ⟹ j > 0; i > 0 ⟹ j < n。所以2、3两种情况可以变为:

2. i < m and B[j−1] > A[i]。意味着 i 太小,我们需要增大它
3. i > 0 and A[i−1] > B[j]。意味着 i 太大,我们需要降低它

代码实现
    public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;

        if (m > n) {
            return findMedianSortedArrays(nums2, nums1);
        }

        int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
        while (iMin <= iMax) {
            int i = (iMin + iMax) / 2;
            int j = halfLen - i;

            if (i < iMax && nums2[j - 1] > nums1[i]) {
                // the i is too small
                iMin = i + 1;
            } else if (i > 0 && nums1[i - 1] > nums2[j]) {
                // the i is too big
                iMax = i - 1;
            } else {
                // the result is perfect
                int maxLeft = 0;
                if (i == 0) {
                    maxLeft = nums2[j - 1];
                } else if (j == 0) {
                    maxLeft = nums1[i - 1];
                } else {
                    maxLeft = Math.max(nums1[i - 1], nums2[j - 1]);
                }

                if ((m + n) % 2 == 1) {
                    return maxLeft;
                }

                int minRight = 0;
                if (i == m) {
                    minRight = nums2[j];
                } else if (j == n) {
                    minRight = nums1[i];
                } else {
                    minRight = Math.min(nums1[i], nums2[j]);
                }
                return (maxLeft + minRight) / 2.0;
            }
        }

        return 0.0;
    }

总结

此算法对我来说比较难理解,该也是LeetCode上难度系数为hard的题目,我这个也是根据LeetCode提供的英文版Solution改编而来,大部分一样,只不过我写成了中文版,当然其中也有我的一些理解。如果大家阅读时,感觉理解起来比较困难,可以先尝试多读几次,然后把代码debug一遍,仔细的查看每一个步骤。这里也附上LeetCode提供的原版Solution连接

后面还会接着发布另外一个算法,也是二分法思想,只不过代码实现起来不同。

注:如果文章有任何不足或错误,请留言!看到后我会尽快回复,谢谢!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值