一、题目介绍
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
·nums1.length == m
·nums2.length == n
·0 <= m <= 1000
·0 <= n <= 1000
·1 <= m + n <= 2000
·-106 <= nums1[i], nums2[i] <= 106
二、题解
2.1二分查找
2.1.1思路
由于两数组长度和有奇数和偶数两种情况,因此所求中位数有直接返回第(m+n)/2小的数据和返回[((m+n)/2)+((m+n)/2+1)]/2两种情况,则该问题可简化为寻找第k小的元素,k=(m+n)/2)或k=(m+n)/2+1)。
由于该题目控制了时间复杂度为O(log (m+n)),因此无法使用合并数组查找的方式求解(该方法时间复杂度为O(m+n)),因此我们使用伪动态数组的方法求解。
伪动态数组的大概思想是通过判断num1[k/2-1]和num2[k/2-1]大小来进一步缩短数组并修改待查数的位置k,进而减少查找次数,降低时间复杂度。
在伪动态数组的处理中可能会出现一些特殊情况,及某个数组读取完毕但仍未找到满足的数据(继续读取数组越界),某个数组为空数组,以及k=1的情况。
①越界问题:根据越界前最后一次排除工作更新k的值,而不能像常规那样直接变为k/2。
②空数组:最好解决的情况->直接到另一个非空数组查找所需的数据。
③k=1:对比排除后的新数组的第一位,返回较小的值(k=1意味着寻找两个动态数组中最小的值)。
操作实例:
越界情况示例:
2.1.2代码
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int sum=nums1.size()+nums2.size();
if(sum%2==1){//针对不同总长要返回不同处理的中位数
return getKthElement(nums1,nums2,(sum+1)/2);
}
else{
return (getKthElement(nums1,nums2,sum/2)+getKthElement(nums1,nums2,sum/2+1))/2.0;
}
}
int getKthElement(vector<int>& nums1, vector<int>& nums2,int k){
int m=nums1.size();
int n=nums2.size();
int index1=0,index2=0;//伪动态数组的头指针
while(true){
if(index1==m){//num1数组已经越界,所以中位数一定在num2数组中
return nums2[index2+k-1];//找第k个数=找动态下标为k-1的数
}
if(index2==n){
return nums1[index1+k-1];
}
if(k==1){//k=1判断
return min(nums1[index1],nums2[index2]);
}
//一般情况
int newIndex1=min(index1+k/2-1,m-1);//防止越界
int newIndex2=min(index2+k/2-1,n-1);
int pivot1=nums1[newIndex1];//待比较的中值->下标为k/2-1的数据
int pivot2=nums2[newIndex2];
if(pivot1<=pivot2){
k-=newIndex1-index1+1;
//更新k,直接减去排查掉的数据量,减少对越界情况的k处理的一步
index1=newIndex1+1;//伪动态数组的头指针
}
else{
k-=newIndex2-index2+1;
index2=newIndex2+1;
}
}
}
};
2.1.3代码分析
还得靠数学好(bushi)
通过伪动态数组的处理大大减少了数据判断的次数,将时间复杂度降低为O(log(m+n))。
2.2划分数组
2.2.1思路
核心思想:二分查找
将nums1和nums2近似对半分开,使nums1形成nums1[0]~nums1[i-1]和nums1[i]~nums1[m-1](nums1的长度为m),同理将nums2分成nums2[0]~nums2[j-1]和nums2[j]~nums2[n-1]两部分,则根据正序数组的设定可以得到nums1[i-1]<=nums1[i],nums2[j-1]<=nums2[j]。
因此可以将左右两部分分别合并,即使nums1[0]~nums1[i-1]和nums2[0]~nums2[j-1]成为新的左半部分,nums1[i]~nums1[m-1]和nums2[j]~nums2[n-1]成为新的右半部分,则会有max(nums1[i-1],nums2[j-1])<=min(nums1[i],nums2[j])的结论。
官方图形解释:
对数组nums1简化称为数组A:
left_A | right_A
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
对数组nums2简化称为数组B:
left_B | right_B
B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
合并可获得:
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]
那么针对两数组总长度为奇数的情况有结论:
①左半部分总长=右半部分总长+1
②左半部分最大值<=右半部分最小值
③所求中位数mid=左半部分的最大值
针对两数组总长度为偶数的情况有结论:
①左半部分总长=右半部分总长
②左半部分最大值<=右半部分最小值
③所求中位数mid=(左半部分的最大值+右半部分的最小值)/2
则根据以上这六点可以发现,无论总长奇偶,条件②都是相同的,而条件①和③则可以通过一定形式转为统一:
则整个题目可以简化为不断枚举符合的i值记录对应的j值(使用左右两个双指针,不断像中央逼近,每次判断后砍掉一半不可能满足条件的数据,从而缩小时间复杂度),直到获得左半部分的最大值和右半部分的最小值(直到左右指针反向移动,即左指针跑到右指针的右面,则可以确定查找到两半部分的中介位置)后返回答案。
2.2.2代码
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.size()>nums2.size()){//使整个程序保证形参nums1.size()<=nums2.size()
return findMedianSortedArrays(nums2,nums1);
}
int m=nums1.size();
int n=nums2.size();
int median1=0,median2=0;//median1是左侧最大值,median2是右侧最小值
int left=0,right=m;//用来寻找符合题意的左侧最大值和右侧最小值
while(left<=right){
int i=(left+right)/2;//用于确定nums1[i-1]位置->要直接取中位,那样减一后才是符合题意的nums1左半部分最大位置
int j=(m+n+1)/2-i;//用于确定nusm2[j]的位置
int nums_im1=(i==0?INT_MIN:nums1[i-1]);//nums[i-1]越界(小于0),返回计算机无限小
int nums_i=(i==m?INT_MAX:nums1[i]);//nums[i]越界(大于m),返回计算机无限大
int nums_jm1=(j==0?INT_MIN:nums2[j-1]);//nums[j-1]
int nums_j=(j==n?INT_MAX:nums2[j]);//nums[j]
if(nums_im1<=nums_j){
median1=max(nums_im1,nums_jm1);//左半部分最大值
median2=min(nums_i,nums_j);//右半部分最小值
left=i+1;//更新下标
}
else{
right=i-1;
}
}
return (m+n)%2==0?(median1+median2)/2.0:median1;//判断返回不同情况下的不同结果
}
};
2.2.3代码分析
每次循环使需要判断是数量减少一半,同时因为可以调换实参传递顺序,使长度更短的数组作为形参中的nums1进行判断,因此时间复杂度为O(log min(m,n)),空间复杂度为O(1)。