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的值非负。由于0≤i≤m,并且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[i−1]不存在。此时,我们不需要检查A[i−1]≤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<m⟹j>0且i>0⟹j<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)。