LeetCode week 4 : Median of Two Sorted Arrays

题目

地址:
https://leetcode.com/problems/median-of-two-sorted-arrays/description/
类别: Binary Search
难度: Hard
描述:
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

分析

给定两个长度分别为m和n且排好序(增序)的数组nums1和nums2,求出这两个数组合并后的中位数。

在这道题的讨论区大致有两种思路,一种是将此问题归为求第K小数问题,另一种则是从中位数的定义出发求解。

思路一

原讨论地址:
https://discuss.leetcode.com/topic/5728/share-one-divide-and-conquer-o-log-m-n-method-with-clear-description

已知len(nums1)= m, len(nums2) = n,我们最终要求的中位数即它们合并后的第(m+n)/2小的数(奇偶情况在代码中讨论)。

比较nums1[m/2] 与 nums2[n/2], 若nums1[m/2]<nums2[n/2]成立,则:
(1) 在nums2[n/2]之前至少有 (m/2 + 1 + n/2) 个元素小于它;
(2) 在nums1[m/2]之后至少有 m + n - (m/2 + 1 + n/2) 元素大于它;
因此:
(1)若 k <= (m/2 + 1 + n/2), nums1保持不变,舍弃nums2中点nums2[n/2]及其之后的元素(因为这些元素至少都是第(m/2 + n/2 + 2)小),
然后继续在nums1和删减后的nums2中寻找第(m+n)/2小的数;
(2)若 k > (m/2 + 1 + n/2), nums2保持不变,舍弃nums1中点nums1[m/2]及其之前的元素(因为这些元素都不可能是第k小,即比第k小要小),
然后继续在nums2和删减后的nums1中寻找第 k - (m/2 + 1)小的数。

时间复杂度为O(log(m+n))。
代码实现

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int size1 = nums1.size(), size2 = nums2.size();
        int total = size1 + size2;

        /** use double 2.0 to devide !  if interger, will wrong **/
        return total & 1 ? findKth(nums1, nums2, 0, size1, 0, size2, total/2+1) :
            (findKth(nums1, nums2, 0, size1, 0, size2, total/2 + 1) + findKth(nums1, nums2, 0, size1, 0, size2, total/2))/2.0;
    }

    /** k : k-the element refers to the index (k-1) base index**/
    /** e1 and e2 is open field **/
    int findKth(vector<int>& nums1, vector<int>& nums2, int s1, int e1, int s2, int e2, int k) {
        /** e1-s1 + e2-s2 > k **/
        if(e1 - s1 > e2 - s2)  return findKth(nums2, nums1, s2, e2, s1, e1, k);
        if(s1 == e1) return nums2[s2+k-1];
        if(k==1)  return min(nums1[s1], nums2[s2]);

        int len1 = min(e1-s1, k/2);
        int len2 = k - len1;

        int m1 = s1 + len1 - 1;
        int m2 = s2 + len2 - 1;

        if(nums1[m1] < nums2[m2]) {
            return findKth(nums1, nums2, s1 + len1, e1, s2, s2 + len2, k - len1);
        }
        else if(nums1[m1] > nums2[m2]) {
            return findKth(nums1, nums2, s1, s1 + len1, s2 + len2, e2, k - len2);
        }
        else {
            return nums1[m1];
        }
    }
};
思路二

原讨论地址:
https://discuss.leetcode.com/topic/4996/share-my-o-log-min-m-n-solution-with-explanation
(原帖讨论很详细清晰,有条件建议看原贴)
已知len(nums1)= m, len(nums2) = n,我们最终要求的中位数的意义在于将 合并后的数组划分成长度相等的两个子集,且一个子集的所有元素都大于或等于另一个子集的所有元素。

首先我们考虑在nums1的随机位置将其划分为两部分:

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

因为nums1共有m个元素,所以有m+1( i = 0 ~ m )种切法,且len(left_A) = i, len(right_A) = m - i 。
注意:当i = 0时,left_A为空;当i = m时,right_A为空。

与之类似,在随机位置j将nums2分成两部分:

      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:

      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)

则我们将{nums1,nums2}中的所有元素分成两个等长的部分,一部分总是大于另一部分,此时不难得到median =(max(left_part)+ min(right_part))/ 2。

将上面的条件进一步细化有:

(1) i + j == m - i + n - j (or: m - i + n - j + 1)
若 n>=m, 则当在nums1的划分位置确定后,可以得到nums2的位置:i = 0 ~ m, j = (m + n + 1)/2 - i(n>=m保证j不为负)
(2) B[j-1] <= A[i] and A[i-1] <= B[j]

在这里我们已经将原问题转化为寻求较小数组的合适划分位置使最后得到的划分满足条件,而在寻求这个位置的过程中,我们可以利用二分查找,所以最后的时间复杂度为O(log(min(m,n)) 。
二分查找过程:

(1)置imin = 0,imax = m,然后开始在[imin,imax]间搜索;
(2)置i =(imin + imax)/ 2,j =(m + n + 1)/ 2-i;
(3)现在我们有len(left_part)== len(right_part);

此时有三种情况:
(a)B[j-1] <= A[i] and A[i-1] <= B[j]
即找到了合适划分位置i,停止搜索。
(b)B[j-1] > A[i]
说明A[i]太小,必须增大i以得到“B [j-1] <= A [i]”(因为当i增加时,j会减少,即A[i]增大,B[j-1]减小)。
置imin = i+1,搜索范围变为[i+1, imax],回到步骤(2);
(c)A[i-1] > B[j]
说明A[i-1]太大,必须减小i以得到“A[i-1]<=B[j]”。
置imax = i-1,搜索范围变为[imin, i-1],回到步骤(2);
加上边界条件后如下:
<a> (j == 0 or i == m or B[j-1] <= A[i]) and (i == 0 or j = n or A[i-1] <= B[j])
找到了合适划分位置i,停止搜索。

<b> j > 0 and i < m and B[j - 1] > A[i]
需要增大i(注意i < m 可以推出 j > 0)

<c> i > 0 and j < n and A[i - 1] > B[j]
需要减小i(注意i > 0 可以推出 j < n)

最后找到合适的位置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 

还有另一个讨论思路也与之相同,不过在处理奇偶和边界条件时用到了很巧妙的方法,地址:
https://discuss.leetcode.com/topic/16797/very-concise-o-log-min-m-n-iterative-solution-with-detailed-explanation
代码实现

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int length1 = nums1.size(), length2 = nums2.size();
        if(length1 > length2) return findMedianSortedArrays(nums2, nums1);
        int imin = 0, imax = length1, halfTotalLength = (length1 + length2 + 1) >> 1;
        int i, j, max_of_left, min_of_right;
        while(imin <= imax) {
            i = (imin + imax) >> 1;
            j = halfTotalLength - i;
            if(i < length1 && nums2[j-1] > nums1[i]) {
                imin = i + 1;
            } else if(i > 0 && nums1[i-1] > nums2[j]) {
                imax = i - 1;
            } else {
                if(i == 0) max_of_left = nums2[j-1];
                else if(j == 0) max_of_left = nums1[i-1];
                else max_of_left = max(nums1[i-1], nums2[j-1]);

                if((length1+length2)%2==1) return max_of_left;

                if(i == length1) min_of_right = nums2[j];
                else if(j == length2) min_of_right = nums1[i];
                else min_of_right = min(nums1[i], nums2[j]);

                return (max_of_left + min_of_right) / 2.0;
            }   
        }
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值