leetcode刷题(114)——4. 寻找两个正序数组的中位数

给定两个大小为 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

示例 3:

输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000

示例 4:

输入:nums1 = [], nums2 = [1]
输出:1.00000

算法:
为了解决这个问题,我们需要理解 “中位数的作用是什么”。在统计中,中位数被用来:

将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。

这其中又分为偶数组和奇数组:

奇数组: [2 3 5] 对应的中位数为3

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

先解释下“割”
我们通过切一刀,能够把有序数组分成左右两个部分,切的那一刀就被称为割(Cut),割(Cut)的左右会有两个元素,分别是左边最大值和右边最小值。

我们定义LMax= Max(LeftPart),RMin = Min(RightPart)。

割可以割在两个数中间,也可以割在1个数上,如果割在一个数上,那么这个数即属于左边,也属于右边

奇数组: [2 3 5] 对应的中位数为3,假定割(Cut)在3上,我们可以把3分为2个: [2 (3/3) 5]

因此LMax=3, RMin=3

偶数组: [1 4 7 9] 对应的中位数为 (4 + 7) /2 = 5.5,假定割(Cut)在4和7之间: [1 (4/7) 9]

因此LMax=4, RMin=7

割和第k个元素
一个数组
对于一个有序数组,对于数组A,如果在k的位置割(Cut)一下(不是割(Cut)在两数中间),那么 LMax = RMin = A[k],

两个数组
也就是我们题目的状态,我们要求得两个数组合并成一个有序数组时,第k位的元素

我们设:
Ci为第i个数组的割。

LMaxi为第i个数组割后的左元素.

RMini为第i个数组割后的右元素。

在这里插入图片描述
首先,LMax1<=RMin1,LMax2<=RMin2 这是肯定的,因为数组是有序的,左边肯定小于右边!,而如果割(Cut)在某个数上,则左右相等。

其次,如果我们让LMax1<=RMin2,LMax2<=RMin1 呢
在这里插入图片描述
那么如果左半边全小于右半边,如果左边的元素个数相加刚好等于k, 那么第k个元素就是Max(LMax1, LMax2),这个比较好理解的,因为Max(LMax1, LMax2)肯定是左边k个元素的最大值,因为合并后的数组是有序,第k个元素肯定前面k个元素中最大的那个。

那么如果 LMax1>RMin2,说明数组1的左边元素太大(多),我们把C1减小,C2=k-C1也就相应的增大。LMax2>RMin1同理,把C2减小,C1=k-C2也就相应的增大。

假设k=3

对于

[2 3 5]

[1 4 7 9]

设C1 = 1, 那么C2 = k - C1 = 2

[2 / 3 5]

[1 4 / 7 9]

这时LMax1 =2, RMin1 = 3, LMax2=4, RMin2=7,

从而有LMax2 > RMin1,依据前面的推论,我们要将C1增大,所以我们让C1 = 2,如下:

[2 3 /5]

[1 / 4 7 9]

这时LMax1 =3, RMin1 = 5, LMax2=1, RMin2=4, 满足 LMax1 < RMin2 且 LMax2 < RMin1 , 所以第3个元素为Max(LMax1,LMax2) = 3

两个数组的最大问题是,它们合并后,m+n总数可能为奇, 也可能为偶,所以我们得想法让m+n总是为偶数

通过虚拟加入‘#’,我们让m转换成2m+1 ,n转换成2n+1, 两数之和就变成了2m+2n+2,恒为偶数。

注意是虚拟加,其实根本没这一步,通过下面的转换,我们可以保证虚拟加后每个元素跟原来的元素一一对应

在这里插入图片描述
这么虚拟加后,每个位置可以通过/2得到原来元素的位置:

比如 2,原来在0位,现在是1位,1/2=0

比如 3,原来在1位,现在是3位,3/2=1

比如 5,原来在2位,现在是5位,5/2=2

比如 9,原来在3位,现在是7位,7/2=3

而对于割(Cut),如果割在‘#’上等于割在2个元素之间,割在数字上等于把数字划到2个部分,总是有以下成立:

LMaxi = (Ci-1)/2 位置上的元素
RMini = Ci/2 位置上的元素

例如:

割在3上,C = 3,LMax=a[(3-1)/2]=A[1],RMin=a[3/2] =A[1],刚好都是3的位置!

割在4/7之间‘#’,C = 4,LMax=A[(4-1)/2]=A[1]=4 ,RMin=A[4/2]=A[2]=7

剩下的事情就好办了,把2个数组看做一个虚拟的数组A,A有2m+2n+2个元素,割在m+n+1处,所以我们只需找到m+n+1位置的元素和m+n+2位置的元素就行了。

左边:A[m+n+1] = Max(LMax1,LMax2)

右边:A[m+n+2] = Min(RMin1,RMin2)

==>Mid = (A[m+n+1]+A[m+n+2])/2 = (Max(LMax1,LMax2) + Min(RMin1,RMin2) )/2

最快的割(Cut)是使用二分法,

有2个数组,我们对哪个做二分呢?
根据之前的分析,我们知道了,只要C1或C2确定,另外一个也就确定了。这里,为了效率,我们肯定是选长度较短的做二分,假设为C1。

LMax1>RMin2,把C1减小,C2增大。—> C1向左二分

LMax2>RMin1,把C1增大,C2减小。—> C1向右二分

如果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割后的右边是空了,为了让LMax1 < RMin2 恒成立,我们可以假定RMin2 = INT_MAX

class Solution {
    
    
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    
    
        int n = nums1.length;
        int m = nums2.length;
        if(n>m){
    
    
            return findMedianSortedArrays(nums2,nums1);
        }
        int LMax1=0, LMax2=0, RMin1=0, RMin2=0, c1=0, c2=0, lo = 0, hi = 2 * n; 

		while (lo <= hi)   //二分
		{
    
    
			c1 = (lo + hi) / 2;  //c1是二分的结果
			c2 = m + n - c1;

			LMax1 = (c1 == 0) ? Integer.MIN_VALUE : nums1[(c1 - 1) / 2];
			RMin1 = (c1 == 2 * n) ? Integer.MAX_VALUE : nums1[c1 / 2];
			LMax2 = (c2 == 0) ? Integer.MIN_VALUE : nums2[(c2 - 1) / 2];
			RMin2 = (c2 == 2 * m) ? Integer.MAX_VALUE : nums2[c2 / 2];

			if (LMax1 > RMin2)
				hi = c1 - 1;
			else if (LMax2 > RMin1)
				lo = c1 + 1;
			else
				break;
		}
		return (Math.max(LMax1, LMax2) + Math.min(RMin1, RMin2)) / 2.0;
    }
}

猜你喜欢

转载自blog.csdn.net/u012124438/article/details/113823331