题目:
题目思路:
这个题,乍一看感觉题真简单啊,无非就是合并两个数组,然后判断合并后的数组长度是奇数还是偶数,然后求一下中位数就可以了。但是如果这种思路去算的话,时间复杂度是不符合题意的。
按照这个思路来的话,代码可以写成下边这样:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
double midNums = 0;
for(auto ii = nums2.begin(); ii!=nums2.end();ii++){
nums1.push_back(*ii);
}
sort(nums1.begin(),nums1.end());
if(nums1.size()%2 == 0){
midNums = (nums1[nums1.size()/2-1] + nums1[nums1.size()/2]) / 2.0;
}
else{
midNums = nums1[nums1.size()/2.0];
}
return midNums;
}
};
这个时间复杂度应该是nlog2n了吧,毕竟用了sort排序。所以要实现题目中的O(log(m+n))应该是需要二分法思想,结合b站大佬(LeetCode004-两个有序数组的中位数-最优算法代码讲解_哔哩哔哩_bilibili)视频讲解,(划分数组解法)思路为:
从图中的两个例子来看整个的解题思路:
(1)当两个有序数组的长度和为奇数时,m+n=15,这时正确的边界划分就像图里显示的一样,此时就能求得正确的中位数,也就是边界左边的第八个数字(最大的数字)。划分的样子就是,有序数组 m 中划分 i 个数字,有序数组 n 中划分 j 个数字。
(2)当两个有序数组的长度和为偶数时,m+n=12,这时正确的边界划分也像图里显示的一样,此时就可以求得正确的中位数,就是边界左边第六个数字(最大的数字)和边界右边第七个数字(最小的数字)的和的一半。
(3)最重要的逻辑其实就是如何正确划分这个 i、j 组成的边界,如何正确划分 i、j 的个数呢?可以理解为“猜”,首先我们可以看出来,i、j 的和和 m、n 的关系:
①当m+n为奇数时,i+j=(m+n+1)/2;
②当m+n为偶数时,i+j=(m+n)/2;
然后我们可以举个例子:
例:m=2,n=2,m+n是偶数,但是m+n+1=5,5/2还是等于2;
所以我们为了更加方便统一,就可以不去管 m+n 的奇偶,统一都用奇数的公式,这样的话之后写代码就简单很多,而且结论也是不会变的,
所以: i+j=(m+n+1)/2
j=(m+n+1)/2-i
结论:因为 m+n 是常熟,已知量,所以只需要求得 i 的值就可以确定 j 的值,所以问题关键就转换为了求 i 的值,那么i的值该怎么求呢?
求 i 的值,实际上就是利用二分法,在有序数组 m 中猜,猜 i 的值可以是什么,根据相应的规则去判断 i 符合条件的值,从而确定 j 的值。
现在的思路很清晰了,问题就剩下,怎么判定 i 的值是符合条件的呢?
步骤:
①以上边的m=6,n=9为例子,先令i=m/2=3,此时j=5,此时num1[i+1]=6<num2[j]=8,这个时候的中位数就不能是左边界的第八个数了,因为右边界的第一个数是小于左边界的第八个数的,所以右边界的第一个数,也就是图里的数字 6 肯定是在中位数左边,就证明此时边界划分选择的 i 是小于标准的
②所以下一步就应该把 i 往右移动,下一个 i 按照二分法的原理,就应该猜测 3+1与6的中间值,也就是 i =5,此时 j = (m+n+1)/2 - i = 3,如下图所示一样。
这时的num1[i+1]=10>num2[j]=5,这是符合条件要求的,但是num1[i]=9>num2[j+1]=7,这里就不符合条件要求了,为什么呢?因为有边界中的数字一定是要比左边界中的数字大的,这样边界上的数字才会是中位数,所以这时的边界 i 划分的太大了,所以要往左走一个,也就是 i=4,此时 j=4。
此时的num1[i+1]=9>num2[j]=7 && num1[i]=6 < num2[j+1]=8,这时就可以确定右边界中的数字全部都是大于左边界的,所以此时的中位数就是,num1[i]和num2[j]里边最大的那个。刨除边界极限的特殊情况,整体的思路就是这样,这时的时间复杂度显而易见在有序数组 m 中不停的二分查找 i 的时间复杂度O(logm) ——最优方法。
思路转换为代码后:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
//首先得定义两个变量m,n分别代表两个数组的长度
int m = nums1.size();
int n = nums2.size();
//这里为了避免数组越界,我就先判断了如果有序数组nums1或者nums2是空的的话,求中位数就只需要判断另一个数组的奇偶了
if (m == 0) {
if (n % 2 == 0) { return (nums2[n / 2 - 1] + nums2[n / 2]) / 2.0; }
else{ return (nums2[n / 2]); }
}
if (n == 0) {
if (m % 2 == 0) { return (nums1[m / 2 - 1] + nums1[m / 2]) / 2.0; }
else { return (nums1[m / 2]); }
}
//然后按照思路来进行边界i的猜测了,首先定义两个变量imin和imax代表二分法猜测i时候的左边界和右边界
//这里把m假设为最小,如果实际m>n了就利用递归方法,交换有序数组nums1和nums2的内容
if (m > n) {
return findMedianSortedArrays(nums2, nums1);
}
int imin = 0;
int imax = m;
while(imin<=imax)//利用while循环,不停的猜测i的值,直到满足i的标准,然后就return了
{
//首先要确定什么时候i的值猜测大了呢?当nums2[j]<nums1[i-1](右边的最小值小于左边的最大值的时候)这时的i就证明猜测的太大了,所以要让imax-1,此时i不能等于0,一是数组会越界,二是i=0了,只需要管nums2的内容就行了。同理,j也不能等于n。
int i = (imin + imax) / 2;//二分法猜测i
int j = (m + n + 1) / 2 - i;//根据i来确定j的值
if (i != 0 && j != n && nums2[j] < nums1[i - 1]) {
imax = i - 1;
}
else if (j != 0 && i != m && nums1[i] < nums2[j - 1]) {
imin = i + 1;
}
else{
//经过上边的判断后,i的值就猜对了,猜对之后按照之前的思路就可以确定j的值了,然后就需要确定边界线左右的值了。
//边界线左边的值定义为leftMax
int leftMax = 0;
//然后求这个leftMax,分三种情况,第一种是i为0的时候,i=0想象一下,m中就没有边界线了,都再n里边,边界最左边的值就是也就是nums2[j-1]了。同理,j=0的时候也一样。
if (i == 0) { leftMax = nums2[j - 1]; }
else if (j == 0) { leftMax = nums1[i - 1]; }
else{ leftMax = max(nums1[i - 1], nums2[j - 1]); }
//同理,rightMin的值就是一样的思路算的了
int rightMin = 0;
if (i == m) { rightMin = nums2[j]; }
else if (j == n) { rightMin = nums1[i]; }
else{ rightMin = min(nums2[j],nums1[i]); }
if ((m + n) % 2 != 0)
{
return leftMax;
}
else{
return (leftMax + rightMin) / 2.0;
}
}
}
return false;
}
留个标记,等能力提升上来之后,把求第k小数这个方法也实现了它!