两个有序数组的中位数问题
———leetcode 第4题
来自leetcode上的答案的整理。
Description:
Median of Two Sorted Arrays
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.
描述:有两个非降有序数组,大小分别是m,n,找这两个数组的中位数。假设这两个数组不能全为空。
a) 暴力破解方式
开辟一个新数组,大小为m+n,用归并的方式将两个数组重新排序的一个数组内,然后找中位数,代码比较容易。
b) 不断减少问题规模得到解
问题抽象:
对于两个有序数组A,B,要找集合{A, B}中kth的数,这里找的是中位数( k=(m+n+1)/2 or k=( (m+n+1)/2+(m+n+2)/2 )/2 ).
A的长度为m,B的长度为n,确保m<=n(如果不满足),首先我们分别找这两个数组的k/2位置的元素,我们将会遇到两种情况:
-
A[k/2]>B[k/2],因为是有序的,那么中位数不会位于B[k/2]之前的部分,变成了一个问题规模更小的子问题。
B[0], B[1], …, B[k/2] | B[k/2+1], B[k/2+2], …, B[n-1]
这时候A没有约减,考虑到我们去掉B[0]~B[k/2],去掉了k/2个最小的数了,我们要在新的数组内找(k-k/2)大的数。
然后不停递归比较约减下去。
-
A[k/2]<=B[k/2],因为是有序的,那么中位数不会位于A[k/2]之前的部分,问题规模得以简化。
A[0], A[1], …, A[k/2] | A[k/2+1], A[k/2+2], …, A[m-1]
这时候B没有约减,同样也是去掉了k/2个最小的数了,我们要在新的数组内找(k=k-k/2)大的数。
然后不停递归比较约减下去。
当最后k-k/2=1的时候,表示我们找到了我们要找的那个数。
伪代码:
function findMedianSortedArrays(A,B):
m=A.size;
n=B.size;
left=(m+n+1)>>1;
right=(m+n+2)>>1;
return (getkth(A,m,B,n,l)+getkth(A,m,B,n,r))/2.0;
end function;
function getkth(s[],m,l[],n,k):
if m>n then:
return getkth(l,n,s,m,k);
end if;
if m==0 then:
//一个数组全部约减掉,等于在另一个数组找第k大的数。
return l[k-1]
end if;
if k==1 then:
//找最小的数,表示我们约减掉的已经足够多了
//只需要比较约减后的两个数组的首元素哪个最小
return min(s[0], l[0]);
end if;
//决定约减哪个数组
int i=min(m,k/2),j=min(n,k/2);
if s[i-1]>l[j-1] then:
return getkth(s,m, l+j,n-j,k-j);
else
return getkth(s+i,m-i,l,n,k-i);
return 0;
end function;
正确性分析:
正确性内含在伪代码注释和问题分析中的。
时间复杂度分析:
子问题简化式子: T ( n ) = T ( 3 n 4 ) + k , k 是 个 常 数 T(n)=T(\frac{3n}{4})+k,k是个常数 T(n)=T(43n)+k,k是个常数,这里的n=m+n,原问题的规模大小是两个数组长度。
故时间复杂度为: O ( log ( m + n ) ) O(\log{(m+n)}) O(log(m+n))
c) 基于中位数的二分查找
首先理解中位数的用处。
从统计上,中位数被用于:将一个集合划分为两个等长的子集,其中一个子集的元素都大于另一个子集。
对数组A,在i位置切分成两部分,对数组B,在j位置切成两部分,
把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]
同时必须确保这两个集合的长度相等,一个集合内的元素总是大于另一个集合内的元素,即:
- len(left_part)=len(right_part)
- max(left_part)≤min(right_part)
然后,我们就可以把{A, B}内所有元素划分成两个子集,其中一个子集内的所有元素总是大于另一个子集。然后;
m e d i a n = m a x ( l e f t _ p a r t ) + m i n ( r i g h t _ p a r t )