寻找两个有序数组的中位数 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3] nums2 = [2]
则中位数是 2.0 示例 2:
nums1 = [1, 2] nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
算法不怎么强而且刚开始刷leetcode的我,做这个题是完全没有思路的,在看过大牛的题解之后,决定纪录一下方便之后查找
,这个题的难点是算法的时间复杂度为O(log(m+n))
这是一个很经典的分治的题目,关键在于怎么划分
首先解释一个割
我们通过切一下,把数组分成左右两个部分,切的那一刀就被称为割(Cut),割的左右会有两个元素,分别是左边最大值和右遍的最小值,因为是有序的
我们定义L = MAX(leftPart) R=MIN(RightPart)
Ps: 割可以割在两个数之间,也可以割在1个数上,如果割在一个数上,那么这个数属于左边,也属于右边
比如说[1,5,7,9]这个序列,割在5和7之间
[1 5 / 7 9]
中值就是 (5+7) /2 = 6
如果[1 5 7] 这个序列,割在5上,我们可以把5分为2个
[1 (5/5) 7]
那么中值就是(5+5) /2 = 5
这样的话可以保证不管整个数组是偶数个数还是奇数个数都能统一运算
割第k个元素
在k的位置割一下,A{k}就是L.换言之,如果左侧有K个元素,A[K]属于左边部分的最大值
双数组切割
我们设:Ci为第i个数组的割地址,Li为数组左边的最大元素,Ri为第i个数组割后的右元素
如何从双数组中取出第k个元素
我们要让两个数组同时进行二分的话,那么我们得让L1 <= R2 && L2 <= R1
那么左半边需要小于右半边,如果左边元素个数等于k,那么第k个元素就是max(l1,l2) 因为l1 和l2 是左半部分的最大值
如果l1 > r2 , 那么说明数组1的左边元素太大,我们可以试着把c1减小,c2增大,同理如果l2 > r1 ,把c1增大,c2减小
假设k=3
对于
[1 5 7 9 ]
[2 4 5]
C1=2 C2 =1
[1 5/ 7 9]
[2 /4 5]
L1 > R2 说明C1必须要减小,C2要进行增大
所以 C1 =1 C2 = 2
[1 / 5 7 9]
[2 4 /5]
满足了 l1 <= r2 && l2 <= r1 第三个元素就是 3
双数组的奇偶
中值的关键在于,如何处理奇偶性,单个数组的情况,我们已经有了回答,但是双数组该如何处理?
我们可以让数组恒为奇数
通过虚拟加’#'让数组长度变化为2n+1,恒为奇数
我们这么加的好处是可以通过每一个位置进行/2就可以得到之前的位置,
在虚拟数组中表示割
li = (ci-1)/2
ri = ci/2
通过计算刚好可以得到之前的位置
割在5/7之间, C=4 L=(4-1)/2 = 1 R = 4/2 刚好都是之前4和7的位置
把两个数组看做为一个虚拟的数组,其中的元素一共有2m+2n+2个元素
所以接下来我们应该找到m+n+1和m+n+2的位置元素就可以解决这一个问题了
左边算出 A[m+n] = max(l1+l2)
右边是A[m+n+1] = min(r1+r2)
怎么解决这个问题?
我们之前也是分析了让其满足 l1<=r2 && l2<= r1 找到 l1和l2 之中的最大值,r1和r2之间的最小值
- l1 > r2 说明数组1的左部分偏大,那么就把c1向左边二分
- l2 > r1 说明数组2的左部分偏大,那么就把c1 向右二分
如果说c1或者c2已经到头,有个数组完全小于或者大于中值
可能有4种情况:
C1 = 0 —— 数组1整体都比中值大,L1 >R2,中值在2中
C2 = 0 —— 数组1整体都比中值小,L1 <R2,中值在1中
C1 = n*2 —— 数组1整体都比中值小,L1 <R2,中位数在2中
C2 = m*2 —— 数组1整体都比中值大,L1 >R2,中位数在1中
其实,如果我已经确定了数组1是最短的数组,那只有两种情况了,比较好处理:
如果C1 = 0 —> 那么我们缩小L1,L1 = INT_MIN,保证判断正确。
如果C1 = n*2 —> 那么我们增大R1,R1 = INT_MAX,保证判断正确。
class Solution {
public:
//对于两个数组同时进行二分查找,排除掉区间
//或者使用一下二分查找
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.size() == 0)
return MedofArray(nums2);
if(nums2.size() == 0)
return MedofArray(nums1);
int m = nums1.size();
int n = nums2.size();
if(m >n)
return findMedianSortedArrays(nums2,nums1); // 对于数组长度最短的进行二分
// 选取最小的进行二分查找
//使用Manacher算法 // 进行扩充
int l1,l2,r1,r2,c1,c2;
//c1,c2 是割的地方 l1 是其中一个数组割的左边,r是割的右边
int left = 0;
int right = 2 * m ; // 进行二分查找
//需要考虑一下越界的问题
//c1 = 0 数组1都比中值大,L1 > R2 , 中值肯定在2中
//c2 = 0 数组1整体都比中值小 L1 < R2 中值在1中
while(left <= right)
{
c1 = (left + right) / 2 ;//c1存储二分结果
c2 = m+n -c1; // c2的大小结果
l1 = (c1 == 0)?INT_MIN:nums1[(c1-1)/2]; // 求中位数
r1 = (c1 == 2*m)?INT_MAX:nums1[c1/2]; // mid
l2 = (c2 == 0 )?INT_MIN:nums2[(c2-1)/2];
r2 = (c2 == 2 *n)?INT_MAX:nums2[c2/2];
if(l1 >r2)
right = c1 - 1;
else if(l2 > r1)
left = c1 + 1; //进行二分
else
break;
}
return (max(l1,l2)+ min(r1,r2))/2.0;
}
// 应该再创一个函数处理其中一个数组为空的情况
double MedofArray(vector<int> & nums)
{
if(nums.size() == 0)
return -1;
return (nums[nums.size()/2] + nums[(nums.size() - 1) / 2] ) /2.0;
}
};