美文网首页
〔两行哥算法系列〕两个排序数组的中位数(二)

〔两行哥算法系列〕两个排序数组的中位数(二)

作者: 两行哥 | 来源:发表于2018-09-13 20:17 被阅读49次

来看看这道算法题(摘自腾讯2018秋招精选):
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 min(m,n)).
You may assume nums1 and nums2 cannot be both empty.

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

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

示例 1:

nums1 = [1, 3]
nums2 = [2]
中位数是 2.0

示例 2:

nums1 = [1, 2]
nums2 = [3, 4]
中位数是 (2 + 3)/2 = 2.5

首先,我们看到题目的要求:算法的时间复杂度为 O(log min(m,n)) ,说明这道题有一个隐性要求:使用二分法查找。还需要留意,题目中给出的两个数组为Sorted,即有序数组。OK,我们开始分析题目。

首先,我们需要明白中位数的数学定义:

定义1:中位数,又称中点数,中值。中数是按顺序排列的一组数据中居于中间位置的数,即在这组数据中,有一半的数据比它大,有一半的数据比它小。

定义2:如果我们把一组有序数据分为两个长度一样的集合(或者两个集合长度差为1),其中一个集合的任意值都小于另外一个集合中的最小值,此时分界点值即为中位值。

其次,根据数学定义对目标数组进行分割并推导解题条件:

题目中已经给出长度分别为m和n的有序数组,用arrayA与arrayB表示这两个数组,并假定m<=n(如果m>n,就将两个数组交换,然后再进行分析)
现在我们将arrayA在i索引处进行分割,得到两个子数组:

subArrayA1 = [ a0 , a1 , a2 ...... , a(i - 1) ],共i个元素
subArrayA2 = [ a(i) , a(i + 1) , a(i + 2) ...... , a(m - 1) ],共m - i个元素

此时,i 的取值范围为[0 , m],当i = 0时,subArrayA1为空数组,当i = m时,subArrayA2为空数组。
由于数组长度为m,可以知道共有m + 1种分割方法。

同理,我们对arrayB进行分割,得到两个子数组:

subArrayB1 = [ b0 , b1 , b2 ...... , b(j - 1) ],共j个元素
subArrayB2 = [ b(j) , b(j + 1) , b(j + 2) ...... , b(n - 1) ],共n - j个元素

此时,j 的取值范围为[0 , n],当i = 0时,subArrayB1为空数组,当i = n时,subArrayB2为空数组。
由于数组长度为n,可以知道共有n + 1种分割方法。

这时候我们将subArrayA1和subArrayB1放入同一个集合leftArray,将subArrayA2和subArrayB2放入另外一个集合rightArray,如果满足如下条件:

条件1:leftArray.length == rightArray.length (或 leftArray.length == rightArray.length +- 1)
条件2:max(leftArray) <= min(rightArray)

那么,我们已经将arrayA和arrayB中所有的元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素。根据上文定义1和定义2,我们可以发现此时的leftArray和rightArray即满足中位值分割,我们就可以求出相应的中位值。

接着,根据条件推导变量 i 与 j 之间的数学关系:

我们对条件1和条件2进行分析,可以求出此时i , j , m , n之间的数量关系,如下:

由条件1可得:i + j = ( m - i ) + ( n - j ) (或 i + j = ( m - i ) + ( n - j ) +- 1),这里 i 的取值范围为[0 , m],并且上文也已经声明m <= n,此时 j = (m + n) / 2 - i
由条件2可得:a(i - 1) <= b(j)并且b(j - 1) <= a(i)

注意:
1.本文开头声明m <= n,如果不满足,则交换两个数组。为什么呢?因为 i 的取值范围为[0 , m], j = (m + n) / 2 - i,当 i 取最大值m时,如果 m > n ,就会导致 j 的值小于0,这是错误的。
2.这里忽略了临界点:i = 0 或 j = 0 或 i = m 或 j = n,始终假定a(i - 1) 、a(i)、b(j - 1)、b(j)存在。关于临界点将在后文进行处理。

最后,我们根据推导出的数学关系,总结算法要求:

在[ 0 , m ]中,找到值 i ,使得:
a(i - 1) <= b(j) 并且 b(j - 1) <= a(i) ,这里的 j = (m + n) / 2 - i
隐性要求:使用二分法查找 i

可能有些读者对二分法查找不太熟悉,这里两行哥先带大家梳理一下查找逻辑:
设iMin=0,iMax=m,然后在[iMin,iMax]中进行搜索。
令 i =iMin + (iMin + iMax) / 2 , j = (m + n) / 2 - i,此时已经满足:
leftArray.length == rightArray.length (或 leftArray.length == rightArray.length +- 1)。
接下来只会遇到三种情况:

1. a(i - 1) <= b(j) 并且 b(j - 1) <= a(i):

这意味着我们找到了目标 i ,可以停止搜索。

2.b(j - 1) > a(i):

这意味着 a(i)太小,我们必须调整 i 以使 b(j - 1) <= a(i)。
我们可以增大 i 吗?
是的,因为当 i 被增大的时候,j 就会被减小。
因此 b(j - 1) 会减小,而 a(i) 会增大,那么 b(j - 1) <= a(i) 就可能被满足。
我们可以减小 i 吗?
不行,因为当 i 被减小的时候,j 就会被增大。
因此 b(j - 1) 会增大,而 a(i) 会减小,那么 b(j - 1) <= a(i) 就不可能满足。
所以我们必须增大 i 。也就是说,我们必须将搜索范围调整为 [i + 1 , iMax],并重新开始搜索。

3.a(i - 1) > b(j):

这意味着 a(i - 1) 太大,我们必须减小 i 以使 a(i - 1) <= b(j) 。 也就是说,我们必须将搜索范围调整为 [iMin , i−1],并重新开始搜索。

当找到目标对象 i 时,则可以分析出中位数:

如果m + n为偶数,则中位数为(Max(leftArray) + Min(rightArray)) / 2
如果m + n为奇数,此时leftArray.length与rightArray.length长度差为1,有如下两种情况:
  1. leftArray.length > rightArray.length,中位数为Max(leftArray)
  2. leftArray.length < rightArray.length,中位数为Min(rightArray)

最后让我们来分析一下遗留问题,当i = 0 或 j = 0 或 i = m 或 j = n 时,a(i - 1) 、a(i)、b(j - 1)、b(j)则可能不存在。通常情况下,我们需要分析a(i - 1) <= b(j) 并且 b(j - 1) <= a(i) 这两个条件是否成立,如果有一个条件中某个元素不存在,则只需要分析另外一个条件是否成立。比如,当i = 0 时, a(i - 1)不存在,此时只需要分析b(j - 1) <= a(i)是否成立。请读者仔细想想,是不是这样?

最后附上代码实现:

    public int[] A = {2, 7, 11, 15};
    public int[] B = {3, 8, 12, 16, 18};


    public double getResult() {
        int m = A.length;
        int n = B.length;
        if (m > n) { //确保m<=n,否则就将A和B交换
            int[] temp = A;//交换A和B
            A = B;
            B = temp;
            int tmp = m;//交换m和n
            m = n;
            n = tmp;
        }
        int iMin = 0, iMax = m;
        while (iMin <= iMax) {//在iMin与iMax之间进行二分查找
            int i = iMin + (iMax - iMin) / 2;//取iMin与iMax的中间值作为i的值
            int j = (m + n) / 2 - i;//根据i的值算出j的值
            if (B[j - 1] > A[i]) {//j过大,i过小,将i的最小值重新赋值
                iMin = i + 1;
            } else if (A[i - 1] > B[j]) {//i过大,j过小,将i的最大值重新赋值
                iMax = i - 1;
            } else {//找到合适的i
                int maxLeft;//寻找leftArray的最大值
                if (i == 0) {
                    maxLeft = B[j - 1];
                } else if (j == 0) {
                    maxLeft = A[i - 1];
                } else {
                    maxLeft = Math.max(A[i - 1], B[j - 1]);
                }

                int minRight;//寻找rightArray的最小值
                if (i == m) {
                    minRight = B[j];
                } else if (j == n) {
                    minRight = A[i];
                } else {
                    minRight = Math.min(B[j], A[i]);
                }

                if ((i + j) < (m + n - i - j)) {//当leftArray长度小于rightArray
                    return minRight;
                } else if ((i + j) > (m + n - i - j)) {//当leftArray长度大于rightArray
                    return maxLeft;
                } else {//当leftArray和rightArray长度一致
                    return (maxLeft + minRight) / 2.0;
                }
            }
        }
        return 0.0;
    }

我们对算法复杂度进行分析:
因为采用了二分法查找,在最坏的情况下,算法的时间复杂度为O(log min(m , n)),底数为2。
我们在算法中额外定义了9个临时变量,分别为int m、int n、int temp、int iMin、int iMax、int i、int j、int maxLeft、int minRight,因此算法的空间复杂度为O(1)。

这篇文章就讲到这里,核心思想就两个,一个是理解中位数的含义,另外一个就是要通过算法复杂度为O(log min(m,n))发现题目隐含条件:使用二分法查找,不可以枚举查找。

相关文章

  • 〔两行哥算法系列〕两个排序数组的中位数(二)

    来看看这道算法题(摘自腾讯2018秋招精选):There are two sorted arrays nums1 ...

  • #4 Median of Two Sorted Arrays

    在两个有序数组中寻找中位数,思想时归并排序的思想,将两个数组归并排序到一个数组中,提前算出中位数的个数减少循环次数

  • LeetCode 经典】MedianSortedArrays

    【LeetCode 经典】MedianSortedArrays 前言 在两个排序数组中寻找中位数。这个题目本质上二...

  • 两个排序数组的中位数

    两个排序数组的中位数 给定两个大小为 m 和 n 的有序数组nums1和nums2。 请找出这两个有序数组的中位数...

  • LintCode 80 [Median]

    原题 给定一个未排序的整数数组,找到其中位数。中位数是排序后数组的中间值,如果数组的个数是偶数个,则返回排序后数组...

  • Lintcode-中位数

    问题描述: 给定一个未排序的整数数组,找到其中位数。中位数是排序后数组的中间值,如果数组的个数是偶数个,则返回排序...

  • 中位数

    给定一个未排序的整数数组,找到其中位数。中位数是排序后数组的中间值,如果数组的个数是偶数个,则返回排序后数组的第N...

  • OJ lintcode 中位数

    给定一个未排序的整数数组,找到其中位数。中位数是排序后数组的中间值,如果数组的个数是偶数个,则返回排序后数组的第N...

  • Java基本算法——二分查找算法

    二分查找算法 每次查找取数组中位数的值进行比较,如果目标值值大于中位数的值,则截取中位数右侧的数组再次进行二分查找...

  • 两个排序数组的中位数

    这是一道经典的数组类型的题目,利用的二分查找(Binary Search )。 4.两个排序数组的中位数(Leet...

网友评论

      本文标题:〔两行哥算法系列〕两个排序数组的中位数(二)

      本文链接:https://www.haomeiwen.com/subject/ukbwgftx.html