思路一(暴力):
当看到这个题目的时候可能会觉的是不是系统高估了这个题目,这个这么简单,只需要将两个数组合并,排序然后合并就好了。这样做确实可以求出中位数,但是并不能说是完成题目的要求,因为题目的要求时间复杂度是O(log(m + n)),这个暴力算法的时间复杂度很明显是O(m+n),可以有兴趣的可以测一下,这个算法不是本博客的重点,这里不多赘述,代码如下:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
double ret = -1;
vector<double> buff;
//合并两个数组
for (auto e : nums1)
buff.push_back(e);
for (auto a : nums2)
buff.push_back(a);
//将合并后的结果进行排序
sort(buff.begin(), buff.end());
int size3 = buff.size();
//获取中位数
if (size3 % 2 == 0)
{
ret = ((buff[size3 / 2] + buff[size3 / 2 - 1]) / 2);
}
else
ret = buff[size3 / 2];
return ret;
}
思路二:分治+二分查找
题目解析:
这个题目简单来说就是如果将两个已经排序的数组合并为一个虚拟数组,求出这个虚拟数组的中位数即可。
解题思路:
1、这道题最主要的就是切(cut),怎么将数组切成合适的两段是关键,对于一个数组来说在数组的中间将其切成两段,这时候就要分情况讨论,如果是偶数个,中位数就是切点的两边第一个数的平均值,如果是奇数个,中位数就是切点右边的第一个数,比如说1 2 3 4 5,在中间的位置将这个数组切成两段:1 2 \ 3 4 5,很显然,中位数就是3,如果是1 2 3 4,那么就切成了1 2 \ 3 4,很显然中位数就是(2+3)/2 = 2.5。
2、理解了切的思想,接下来就开始在两个数组中进行切,这时候就用到了分治思想。
怎么分?
题目中要求的时间复杂度为 O(log(m + n)),很容易想到的方法就是二分,现在有两个数组,要对那个数组进行二分合适?由于找的是中位数,那么这个数字的两边的元素个数是相等的,所以只需要确定一个数组中的两边元素,两一个数组的对应的补上去就可以了,为了提高效率,要选择最短的数组做二分查找。
怎么治?
这个也很容易,只需要分别比较两个数组切点两边的数就可以,假设数组1中切点两边的元素为L1,R1,数组2中切点两边的元素为:L2,R2,切完之后有三种情况:
1)L1>R2 ,说明数组1的左半边比数组2的右半边大,应该让cut向左移,才能使数组一中较多的数被分配到右边。
2)L2>R1 ,说明数组2的左半边比数组1的右半边大,应该让cut向右移,才能使数组一中较多的数被分配到左边。
3)其他情况(L1<=R2 L2<=R1),cut的位置是正确的,可以停止查找,输出结果。
3.特殊情况
分析完了正常的情况,那么就要分析一下特殊情况;
1)如果有一个数组是空的,直接返回另一个不为空的数组中的中位数
2)如果两个数组元素的个数相等,并且两个数组的中位数相等,直接返回其中一个中位数。
3)有可能在进行二分查找的时候出现了数组越界的情况,只需要定义一个最大值和一个最小值,这样可以按照正常的情况来处理了。
4、输出结果分情况
两个数组的输出情况和之前的一个数组的输出大致是一样,只是添加了选择性,如果是偶数个,输出(min(L1, L2) + min(R1, R2))/2,如果是奇数个,输出min(R1, R2)。
图解思路:
这里只对不越界和不为空的数组进行解析。
代码解析:
class Solution {
public:
double MedNum(vector<int>& num)
{
double ret;
size_t size = num.size();
if(size == 1)
return num[0];
int med = size / 2;
if (size% 2 == 0)
{
ret = ((double)(num[med] + num[med - 1]) / 2);
}
else
ret = num[med];
return ret;
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
double ret = -1;
//定义最小数和最大数
const int BDL_MIN_ = 0x80000000;
const int BDL_MAX_ = 0x7fffffff;
//情况一:
//如果这两个数组中有一个是空的,直接返回另一个的中位数即可
if (nums1.empty())
return MedNum(nums2);
else if (nums2.empty())
return MedNum(nums1);
//情况二:
//如果两个数组分别的中位数相等,那么这两个数的中位数就是这个中位数
int size1 = nums1.size(); //数组一的长度
int size2 = nums2.size(); //数组二的长度
if(size1 == size2)
{
Med1 = MedNum(nums1);
Med1 = MedNum(nums1);
if(Med1 == Med2)
return Med1;
}
//情况三:
//使用分治思想,二分查找方法
int size0 = size1 + size2; //两个数组的总长度
//为了效率,要选择最短的数组做二分查找,使数组一为最短
//如果第一个数组比第二个数组长,就要使两个数组交换
//但是swap的时间复杂度使O(N+M),所以将两个数组交换位置再调一次函数即可
if (size1 > size2)
return findMedianSortedArrays(nums2, nums1);
//设置二分查找的范围
int cutL = 0;
int cutR = size1;
//这里将cut1初始为数组1的中位数
int cut1 = (size1 - 1) / 2;
while (cut1 <= size1)
{
cut1 = (cutR - cutL) / 2+cutL; //在数组1中找cut1切点(二分)
int cut2 = (size0 / 2) - cut1; //在数组2中找cut2切点
//这里不用size2直接确定切点,是因为将两个数组进行了虚拟合并,使用虚拟数组的总数size0找中间点,然后将属于数组一的部分减去,就是cut2在数组2中的切点,可以结合上面的图片捋一下。
//确定L1,L2,L3,L4的值,并判断当前的切点有没有越界
double L1 = (cut1 == 0) ? BDL_MIN_ : nums1[cut1-1];
double R1 = (cut1 == size1) ? BDL_MAX_ : nums1[cut1];
double L2 = (cut2 == 0) ? BDL_MIN_ : nums2[cut2-1];
double R2 = (cut2 == size2) ? BDL_MAX_ : nums2[cut2];
//如果L1>R2,则cut1应该向左移,才能使数组1较多的数被分配到右边。
if (L1 > R2)
cutR = cut1-1;
//如果L2 > R1,则cut1应该向右移,才能使数组1较多的数被分配到左边。
else if (L2 > R1)
cutL = cut1+1;
//其他情况就是L1<=R2 L2<=R1,说明当前cut1和cut2的位置就是中位数的位置了
else
{
//如果两个数组中加起来的元素数量是偶数,那么中位数就应该是两个中位数的平均值
if (size0 % 2 == 0)
{
L1 = max(L1, L2);
R1 = min(R1, R2);
ret = (L1 + R1) / 2;
return ret;
}
//如果两个数组中元素的数量是奇数,中位数就是当前位置的右值
else
{
ret = min(R1, R2);
return ret;
}
}
}
return ret;
}
};
如果有什么问题可以在评论区留言!