leetcode 4.median of Two Sorted Arrays

@author stormma
@date 2017/11/05


生命不息,奋斗不止!


** 题目
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

思路1(翻译自Leetcode大神分析)

要解决这个问题,我们需要理解什么是中位数,在统计学中,中位数用于:

切分一个集合为两个长度相等的子集合,并且第一个子集合总是小于第二个子集合。

如果我们理解了这个概念,那么我们就很接近答案了(在我看来并不是)。首先,让我们用一个随机的位置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
注: 当i = 0, left_A是空,当i = m right_A是空

同理,使用随机位置j切分B集合为两个子集合:

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

接下来,让我们把left_A和left_B放入一个集合,把right_A和right_b放入另外一个,并取名第一个集合为左半部分,另外一个为右半部分。

         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(right_part)) / 2

要确保上面的两种情况,我们只需确保:

  1. i + j == m - i + n - j (or: m - i + n - j + 1)
    如果n >= m,我们需要取i = 0 ~ m, j = (m + n + 1) / 2 - i
  2. B[j - 1] <= A[i] 并且 A[i - 1] <= B[j]

注:
1. 为了简便起见,我们现在假设A[i - 1], A[i], B[j - 1], B[j]都是有效的,后面我们再处理边界条件
2. 为什么n >= m ? 因为我们必须确保j是非负的,0 <= i <= m 并且j = (m + n + 1) / 2 - i, 如果n < m, 那么j必定是负数,这并不能让我们得到正确的答案。

所以,总之,我们只需做:

在[0, m]范围寻找一个i的值满足
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 + iMax) / 2,j = (m + n + 1) / 2 -i
3. 现在,我们得到abs (len(left_part) - len(right_part)) <= 1(此处是我修改的,作者原表达式为len(left_part) == len(right_part))。然后,我们会遇到以下三种情况:
- B[j - 1] <= A[i] 并且 A[i - 1] <= B[j],这种情况表明我们得到了我们想要的答案
- B[j - 1] > A[i]这种情况表明,我们A[i]的值过小了,我们应该调整i的值得到B[j - 1] <= A[i],我们怎么调整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],所以设置iMin = i + 1并重复2.
- A[i - 1] >= B[j]: 这个情况表明A[i - 1]过大,我们应该减小i的值,保证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, i = m, j = 0, j = n,当A[i - 1], B[j - 1], A[i], B[j]不存在时,实际上,这种情况要比你想象得更简单

我们应该怎么做来保证max(left_part) <= min(right_part)。如果i 和 j 都不是边界条件时候,那么表明A[i - 1], A[i], B[j - 1], B[j]都存在,然后我们必须检查B[j - 1] <= A[i]并且A[i - 1] <= B[j]。但是如果A[i - 1], A[i], B[j - 1], B[j]不存在,我们不需要检查这两个情况或其中任意一个。举个例子,i = 0, A[i - 1]不存在,我们不需要检查A[i - 1] <= B[j],所以我们应该做以下事情:

在[0, m]查找一个i的值,满足
(j == 0 or i == m or B[j - 1] <= A[i]) 并且
(i = 0 or j == n or A[i - 1] <= B[j]),j = (m + n + 1) / 2 - i

在循环中,我们将会遇到以下三种情形:

  1. (j == 0 or i == m or B[j -1] <= A[i]) and
    (i == 0 or j == n or A[i - 1] <= B[j])
    这个情况表明我们找到了合适的i
  2. i < m and B[j - 1] > A[i]
    表明i的值太小,我们应该增加i的值,通过修改查找范围的下限
  3. j < n and A[i - 1] > B[j]
    表明i的值过大,我们应该减小i,通过修改查找范围的上限

补充:
i < m ⇒ j > 0

m <= n, i < m => j = (m + n + 1) / 2 -i > (m + n + 1) / 2 -m >=(2m + 1) / 2 - m >= 0

i > 0 => j < n

m <= n, i > 0 => j = (m + n + 1) / 2 - i < (m + n + 1) / 2 < (2n + 1) / 2 <= n

代码实现

时间复杂度 log(min(m, n))

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
            if (nums1.length > nums2.length) {
                int[] temp = nums1; nums1 = nums2; nums2 = temp;
            }
            int m = nums1.length, n = nums2.length;
            int iMin = 0, iMax = m, halfLen = (m + n + 1) >> 1;
            while (iMin <= iMax) {
                int i = (iMin + iMax) >> 1;
                int j = halfLen - i;
                if (i > iMin && nums1[i - 1] > nums2[j]) { // i is too big
                    iMax = i - 1;
                } else if (i < iMax && nums2[j - 1] > nums1[i]) { // i is too small
                    iMin = i + 1;
                } else { // find correct i
                    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 (minRight + maxLeft) / 2.0;
                }
            }
            return 0.0;
        }

思路2(递归方式)

其实问题可以转换为在a,b数组中寻找第k大数,那么我们可以先比较a[k / 2 - 1]和b[k / 2 - 1],那么可以得到以下三种情况:

  1. a[k / 2 - 1] < b [k / 2 - 1],这个表明,a数组的k / 2个元素小于ab合并之后的k小元素,为啥呢,因为a[k / 2 - 1] < b k / 2 - 1
  2. a[k / 2 - 1] > b [k / 2 - 1]同理b数组的k / 2个元素小于ab合并之后的k小元素,所以答案不可能出现在这。
  3. a[k / 2 - 1] = b [k / 2 - 1],此时的这个数就是第k小元素

那么我们来看一下边界条件

如果A或者B为空,则直接返回B[k - 1]或者A[k - 1];
如果k为1,我们只需要返回A[0]和B[0]中的较小值;
如果A[k / 2 - 1] = B[k / 2 - 1],返回其中一个;

代码实现

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
            int m = nums1.length, n = nums2.length;
            // 1 2 3 4
            if ((m + n) % 2 == 0) {
                return (findKth(nums1, 0, nums2, 0, (m + n) / 2) + findKth(nums1, 0, nums2, 0,
                        (m + n) / 2 + 1)) / 2.0;
            } else {
                return findKth(nums1, 0, nums2, 0, (m + n + 1) / 2);
            }
        }

        /**
         * 寻找合并数组中第k大元素
         * @param a
         * @param aStart
         * @param b
         * @param bStart
         * @param k
         * @return
         */
        private int findKth(int[] a, int aStart, int[] b, int bStart, int k) {
            if (aStart >= a.length) {
                return b[bStart + k - 1];
            }
            if (bStart >= b.length) {
                return a[aStart + k - 1];
            }
            if (k == 1) {
                return Math.min(a[aStart], b[bStart]);
            }
            int aMid = Integer.MAX_VALUE, bMid = Integer.MAX_VALUE;
            if ((aStart + k / 2 - 1) < a.length) {
                aMid = a[aStart + k / 2 - 1];
            }
            if ((bStart + k / 2 - 1 ) < b.length) {
                bMid = b[bStart + k / 2 - 1];
            }
            // a前k / 2个数大于b 前k / 2个元素,
            if (aMid > bMid) {
                return findKth(a, aStart, b, bStart + k / 2, k - k / 2);
            } else {
                return findKth(a, aStart + k / 2, b, bStart, k - k / 2);
            }
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值