LeetCode 4: Median of Two Sorted Arrays

Description:

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)).

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

描述:

两个有序数组nums1和nums2,数组元素数量分别为m和n。

寻找两个数组的中位数。要求程序的时间复杂度为O(lg(m+n))。

假设数组nums1和nums2不同时为空。

示例1:

  nums1 = [1, 3]

  nums2 = [2]

  中位数为2.0

示例2:

  nums1 = [1, 2]

  nums2 = [3, 4]

  中位数为(2 + 3) / 2 = 2.5

 

方法:

首先,我们先理解中位数的概念和意义。中位数,又称中点数,中值。中数是按顺序排列的一组数据中居于中间位置的数,即在这组数据中,有一半的数据比他大,有一半的数据比他小。对于有限的数集,可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数。

中位数的意义是把一个集合分为两个长度相等的子集合,其中一个子集合总是比另外一个集合大。

假设题中两个数组nums1和nums2分别以集合A和B表示。

首先,在随机位置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)。

子集合left_A的长度为i,子集合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[i], B[i+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[i], B[i+1], ..., B[n - 1]

如果可以保证:

len(left_part)=len(right_part)
max(left_part) ≤ min(right_part)

那么,我们将集合A和集合B分割为两个长度相等的部分,并且left_part总是小于right_part。

并且:

median= (max(left_part)+min(right_part))/2

为了保证上述条件成立,我们需要保证:

1. i+j=m−i+n−j (or: m - i + n - j + 1)
   if n≥m, we just need to set:  i=0∼m, j=(m+n+1)/2−i

2. B[j−1]≤A[i] and A[i−1]≤B[j]

PS1: 为了便于表述,假设A[i-1]、B[j-1]、A[i]和B[j]有效,即使i=0,i=m,j=0和j=n时。后面将阐述如何处理这些情况。

PS2: 为什么要求n>=m?主要是确保j的值非负。由于0im,并且j = (m + n + 1)/2- i。如果n<m, 那么j的值可能为负,导致结果错误。

综上所述,整体思路是:

在区间[0,m]中寻找i的值,使得

B[j−1]≤A[i] and A[i−1]≤B[j],  这里, j = (m + n + 1)/2 - i

可以使用二叉查找的方式查询满足上述条件的i值。

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]且A[i-1] <= B[j]。此时,我们找到正确的结果,停止搜索并退出。
    b. B[j - 1] > A[i]。此时,A[i]的值较小,需要调整i的值使得B[j-1]<=A[i]。增加i的值,导致A[i]的值增加,并且j的值减小,B[j-1]的值也相应减小。开始搜索区间[i + 1, imax]。设置imin的值为i+1,跳转到步骤2。
    c. A[i - 1] > B[j]。此时,以为这A[i-1]的值较大,需要减少i的值,并开始搜索区间[imin, i - 1]。设置imax的值为i-1,并跳转到步骤2。

当寻找到满足上述条件的i值后,中间值的计算方式如下:

max(A[i−1],B[j−1]),  当m + n为奇数时​;
​(max(A[i−1],B[j−1])+min(A[i],B[j])),当n为偶数时。

现在,我们开始考虑边际条件。即i=0或i=m或j=0或j=n时,此时A[i-1]、A[i]、B[j-1]或B[j]的值可能并不存在。

我们需要保证max(A[i−1],B[j−1])<=min(A[i],B[j])。所以当i和j的值均不是边际值时,需要检查B[j-1]<=A[i]且A[i-1]<=B[j]。

如果i或j的值为边际值,那么我们不需要检查相应的边际条件。例如, 如果 i=0, 那么A[i1]不存在。此时,我们不需要检查A[i1]B[j];我们需要做的是:

在区间[0,m]中搜索i的值使得
(j=0或i = m或B[j−1]≤A[i])并且
(i=0或j = n或A[i−1]≤B[j])

在每次搜索循环中,我们遇到以下三种情形:

1. (j=0或i=m或B[j−1]≤A[i]) 并且(i=0或j=n或A[i−1]≤B[j]) 
   表明i为所需要寻找的值,停止搜索。
2. j>0且i<m且B[j−1]>A[i] 
    表明i的值较小,增加i的值
3. i>0且j<n且A[i−1]>B[j] 
    表明i的值较大,减少i的值

此外,由于i<mj>0且i>0j<n。所以在情形2和情形3下我们不需要检查j>0和i>0的条件。

 

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        if(m > n) {
            vector<int> temp = nums1;
            nums1 = nums2;
            nums2 = temp;
            int t = m;
            m = n;
            n = t;
        }
        
        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]){
                iMin = i + 1;
            } else if(i > iMin && nums1[i - 1] > nums2[j]) {
                iMax = i - 1;
            } else {
                int maxLeft = 0;
                if(i == 0) maxLeft = nums2[j - 1];
                else if(j == 0) maxLeft = nums1[i - 1];
                else maxLeft = max(nums1[i - 1], nums2[j - 1]);
                if((m + n) % 2 != 0) return maxLeft;
                
                int minRight = 0;
                if(i == m) minRight = nums2[j];
                else if(j == n) minRight = nums1[i];
                else minRight = min(nums1[i], nums2[j]);
                
                return ((double)minRight + maxLeft) / 2.0;
            }
        }
        return 0.0;
    }
};

本方法的时间复杂度为O(log(min(m,n)),空间复杂度为O(1)。

 

posted @ 2018-08-14 16:02  双肩包码农  阅读(95)  评论(0编辑  收藏  举报