c++ 数组置0_[LeetCode]寻找两个有序数组的中位数

今日闲来无事,拿起电脑上,开始做题。

ccc540d961fd30a08d302ef78be33b30.png

乍看就是一道非常简单的题目,只需要将两个数组归并排序,然后根据合并后的数组长度是奇数还是偶数返回中位数即可。

相关代码如下:

b9653dbf6088431658b89e2566ccd5fd.png

但是我们注意到题目中有一个条件:要求算法的时间复杂度为O(log(m+n))。

上面的代码调用python内置函数sort()对合并后的数组进行排序,实际上python的sort函数底层是用归并排序算法实现。而学过数据结构与算法的都知道,归并排序的时间复杂度并不是O(log(n+m))。

所以,这道题的难点就在于设计一个O(log(n+m))的求中位数的算法。

为了解决这个问题,我们需要理解“中位数的作用是什么”。在统计学中,中位数被用来:将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。

我们根据数组的元素个数将数组分为奇数组个偶数组。例如:

奇数组[2,3,5]对应的中位数是3

偶数组[1,4,7,9]对应的中位数是(4+7)/2=5.5

我们通过切割一刀,能把有序数组分成左右两个部分,那么切的这一刀就被称为,割的左右会有两个元素,分别是左边的最大值和右边的最小值。

我们定义lmax=max(LeftPart),rmin=min(RightPart),分别表示左边最大值和右边最小值。

割可以割在两个数之间,也可以割在一个数上。如果割在一个数上,那么这个数既属于左边,也属于右边。例如:

奇数组[2,3,5]对应的中位数是3,这一刀割在3上,那么可以理解为[2,(3/3),5],即将3分为两个。因此lmax=3,rmin=3。

偶数组[1,4,7,9]对应的中位数是(4+7)/2=5.5,这一刀是割在4和7中间,即[1,(4/7),9]。因此lmax=4,rmin=7。

对于一个长度为n的数组A来说:

我们在任意位置i对数组A进行切割,由于数组A有n个元素,那么我们就有n+1种切割方式,因为i的取值范围是[0,n]。

因此可以得出以下两条信息:

①len(LeftPart)=i,len(RightPart)=n-i。

②当i=0(i=n)时,左(右)边集合为空集。

对于长度为n的数组A和长度为m的数组B:

这种情况就是这道题目的状况。我们要求的就是两个数组合并后切割的那个位置,假定为k。

假设ci为第i个数组的切割点,lmaxi为第i个数组左边子集的最大值,rmini为第i个数组右边子集的最小值。

两个数组切割后如图所示:

444715d9c45fac1644234f8b7804ccf1.png

(手画的草图,偷懒点)

首先,根据数组A和数组B是有序的我们可以得知rmin1≥lmax1,rmin2≥lmax2,如果割在某个数上,则左右相等。

其次,如果我们让rmin2≥lmax1,rmin1≥lmax2,那么左半边元素全部小于右半边元素。如果左边元素个数相加刚好为k,那么第k个元素就是max(lmax1,lmax2)。这个是比较好理解的,因为max(lmax1,lmax2)肯定是左半边所有元素的最大值,因为合并后的数组依旧有序,所以第k个元素就是前k个元素之中最大的那个。

如果lmax1>rmin2,说明对于数组A的切割并不合理,数组A的左边元素太多,故通过减小c1使数组A左边元素变得更合理,而c2=k-c1,c1减小c2增加。同理lmax2>rmin2,我们增加c1减小c2。

解释一下为什么c2=k-c1:因为k是我们所求的合并后的数组C的中位数,故k=(n+m)/2。而c1和c2是数组A和数组B的切割点,如果这两个切割点正好符合所有要求,那么c1左边的元素与c2左边的元素合并后应该是数组C根据中位数切割后的左子集。

举个例子。假设数组A和数组B如下:

数组A:[2,3,5]

数组B:[1,4,7,9]

k=3。设c1=1,则c2=k-c1=2。切割后:

数组A:[2,/ 3,5]

数组B:[1,4,/ 7,9]

这时lmax1=2,rmin1=3,lmax2=4,rmin2=7。从而有lmax2>rmin1,因此我们需要增加c1,让c1=2,如下:

数组A:[2,3,/ 5]

数组B:[1,/ 4,7,9]

这时lmax1=3,rmin1=5,lmax2=1,rmin2=4。满足rmin2≥lmax1且rmin1≥lmax2,故此第三个元素为max(lmax1,lmax2)=3。

现在来思考一个问题,数组A和数组B合并后的长度可能是奇数也可能是偶数,这样后续的讨论必须分为奇数和偶数两种情况。我们有没有办法让这两种情况统一?

答案是有的。我们通过虚拟加入#符号,让数组A的长度变为2n+1,数组B的长度变为2m+1,这样两个数组合并后的长度为2(n+m)+2恒定为偶数。注意这里是虚拟加入#,并没有在内存中再开辟两个新的数组,通过一定的转换,我们可以保证虚拟加入#后每个元素跟原来的元素一一对应。

f01f7d9e6c04585380bcfdd671519c4c.png

虚拟加入#后,每个位置可以通过/2得到原来元素的位置

比如2,原来的位置是0,虚拟加入#后的位置是1,1/2=0。

比如9,原来的位置是3,虚拟加入#后的位置是7,7/2=3。

如果割在#等于割在两个数之间,如果割在数字上等于把数字划到两个部分,总之以下是成立的:

①lmaxi=(ci-1)/2位置上的元素。

②rmini=ci/2位置上的元素。

例如:

割在3上,c=3,lmax=A[(c-1)/2]=A[1],rmin=A[c/2]=A[1]。

割在4和7中间#,c=4,lmax=A[(c-1)/2]=A[1],rmin=A[c/2]=A[2]。

剩下的事情就好办了,把两个数组看做是一个虚拟的数组C,并且有2n+2m+2个元素,割在n+m+1处,所以我们只需要找到第n+m+1个元素和第n+m+2个元素即可。

左边:C[n+m]=max(lmax1,lmax2)

右边:C[n+m+1]=min(rmin1,rmin2)

中位数:(C[n+m]+C[n+m+1])/2

最快的割是二分法。那么该对于哪个数组进行二分呢?根据之前的分析,只要知道c1或c2其中一个,那么另一个也就确定了。故此为了效率,我们选择长度较短的数组做二分。

如果c1或c2已经到头了怎么办?

出现这种情况是因为:有个数组的值完全小于或大于中位数。假定n<m,可能会出现以下4种情况:

①c1=0:数组1整体比中值要大,中值位于数组2,简单来说就是数组1切割后左边集合为空,所以可以假定lmax1=INT_MIN。

②c1=2n:数组1整体比中值要小,中值位于数组2,简单来说就是数组1切割后右边集合为空,所以可以假定rmin1= INT_MAX,来保证lmax2<rmin1恒成立。

③c2=0:数组2整体比中值要大,中值位于数组1,简单来说就是数组2切割后左边集合为空,所以可以假定lmax2=INT_MIN。

④c2=2m:数组2整体比中值要小,中值位于数组1,简单来说就是数组2切割后右边集合为空,所以可以假定rmin2= INT_MAX,来保证lmax1<rmin2恒成立。

相关代码如下:

25b5d01eeb5c7a5af44c4c320151331e.png

到这里这道题目就算是解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值