Leetcode-4 Median of Two Sorted Arrays 做题总结

一、介绍

       这是Leetcode中的第4个题目,可以说,很多按照顺序刷题的同学,刷着刷着,突然就碰到了这个题目,然后就懵逼了。特别是看到了上面说的时间复杂度限制为O(log (m+n))的时候,感觉难以下手。其实我也是哈哈哈,但我没有按照顺序做题,而是在顺序的基础上,做完一个题目之后,根据accepted页面下,Next challenges来做,如图所示:

          这样可以延续你做题的思路,是很好的不断巩固的一种方式。所以,我到了做完60题之后,才碰都这个题目。所以,哈哈哈,经验告诉我,实在不行,就先参考下别人的代码。题目如下:

There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
You may assume nums1 and nums2 cannot be both empty.

Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0

Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5

二、解题思路

       首先,我们考虑两个数组的长度len1+len2为偶数的情况。由于nums1和nums2都是已经排好了序的数组,所以,我们很明显想要找到在一个长度为len1+len2的有序数组,例如:1 2 3 4 5 6 7 8中,去找中间的两个元素4 5,然后取平均值4.5,就是中位数了。但很明显,将两个有序数组合成一个有序数组,其时间复杂度太大了。

        之后,可以想到,既然只要中位数,就是说和之前或者之后的元素,其实关系没有特别大,至少不至于非要像1 2 3 4 5 6 7 8那样排地整整齐齐(这里可别说什么一家人就要整整齐齐的骚话了)。如果是:1 3 2 4 5 7 6 8这样的情况,其实也可以行。这样我们就可以想到,似乎并不需要将两个有序数组合成一个有序数组。 例如下面的例子:

             [1, 2, 4 ]  |  [6*, 8,  9 ]    [a1, a2]               |     [a3, a4, a5, a6]
             [2, 3, 5*]  |  [10, 11, 12]    [b1, b2, b3, b4, b5]   |     [b6, b7, b8]

         我们只要能够保证:分隔符左边的元素都小于右边的元素,且两边的元素各占整体的一半就可以了。从左边那个例子推广到右边那个稍微一般一点的例子,可以发现,只要满足 a2、b5 < a3、b6就可以了。其中a2 < a3 和 b5 < b6是题设中有序数组给的。所以,我们只需要找到这样的一个分割方案,使得:1。 a2 < b6   2。b5 < a3就可以了。当然分割线两边的元素得一样多。 

 有了这样的思路,我们做下面一些变量的声明:

int start = 0;           //指向最左边的切割位置
int end = len1;          //指向最右边的切割位置
int cut1 = 0;            //代表nums1中分割线左边有cut1个元素
int cut2 = 0;            //代表nums2中分割线左边有cut2个元素
int half = (len1 + len2) / 2;   //代表两个分割线左侧元素的个数和。
// 当len1+len2为奇数的时候,右边的数量多一个,所以最终返回右边两侧中较小的那个数就可以

所以,其中最后一条注释,其实也就交代了当len1+len2为奇数时的处理方法。

          现在,问题就转化为了找cut1的值,然后cut2的值,就是half - cut1。再看看时间复杂度的要求:O(log (m+n))。有没有觉得很熟悉,时间复杂度为取对数的查找方法是哪个?

        很明显,每次折中一下,然后继续查找,就是折半查找——二分查找。那么,我们再想一下,你是愿意去在较长的数组,还是较短的数组中去做查找,和明显是较短的嘛。所以,我们开始的时候,可以将nums1设置为两者中较短的那个,然后从中找cut1,然后half - cut1就是cut2的值了。所以时间复杂甚至都变成了O(log (min(m, n)))。从而我们也得到了最优解。

三、附加说明

       为了方便起见,我们将切割线两侧的元素设置为了L1,L2,R1,R2,它们的位置如下。当出现切割位置在数组最前面或者最后的时候,就需要特别处理一下:

             [......L1]  (cut1)   [R1......]
             [......L2]  (cut2)    [R2......]

       举个例子:如果cut1在nums1的最前面了,很明显,L1的值找不到了。由于这样的情况也是满足条件的,毕竟不能再把cut1往左侧走了,所以我们反过来为了满足L1<R2的条件,一不做二不休,干脆给L1赋上Int的最小值。当然,如果cut2在nums1的最右边了,我们就给R1赋上Int的最大值。

        好了,下面就是最精彩的源码环节,还有其他的注释,包括测试用例,也都在里面了。

四、源码

/**
 * @author lei
 * @date 2019.9.26 17:26
 * @description: 找两个排好序的数组中所有元素的中位数
 */
public class MedianOfTwoSortedArrays {
	public static void main(String[] args) {
		System.out.println("test: ");
		//int [] nums1 = {9,                  10, 11, 12, 22};
		//int [] nums2 = {0, 3, 5, 6, 8,         11,    12};
		int [] nums1 = {2};
		int [] nums2 = {2};
 		double res = findMedianSortedArrays(nums1, nums2);
		System.out.println("res = " + res);
	}
	
	public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
		if(nums1.length == 0 && nums2.length == 0)
			return 0;
		//我们为了让时间复杂度变成O(min(log(m), log(n))),选择用二分法遍历nums1和nums2中较短的那个
		if(nums1.length > nums2.length){
			int [] temp = nums1;
			nums1 = nums2;
			nums2 = temp;
		}
		int len1 = nums1.length, len2 = nums2.length;       //获取两个数组新的长度
		boolean even = ((len1 + len2) % 2 == 0);            //记录两个数组的长度和是否是偶数,当是偶数的时候,就需要取两数的平均值。
		int start = 0;           //指向最左边的切割位置
		int end = len1;          //指向最右边的切割位置
		int cut1 = 0;            //代表nums1中分割线左边有cut1个元素
		int cut2 = 0;            //代表nums2中分割线左边有cut2个元素
		int half = (len1 + len2) / 2;   //代表两个分割线左侧元素的个数和。
                //当len1+len2为奇数的时候,右边的数量多一个,所以最终返回右边两侧中较小的那个数就可以
		//使用二分查找,在start 到 end 之间找到cut1的值。很明显,cut2的值就是: half - cut1
		while(start <= end) {
			cut1 = start + (end - start) / 2;
			cut2 = half - cut1;
			int L1 = (cut1 == 0) ? Integer.MIN_VALUE : nums1[cut1 - 1];//[...L1](cut1)[R1...]
			int L2 = (cut2 == 0) ? Integer.MIN_VALUE : nums2[cut2 - 1];//[...L2](cut2)[R2...]
			int R1 = (cut1 == len1) ? Integer.MAX_VALUE : nums1[cut1];
			int R2 = (cut2 == len2) ? Integer.MAX_VALUE : nums2[cut2];
			//因为有相等的情况,所以先排除需要移动end 或者start的情况
			if (L1 > R2)
				end = cut1 - 1;
			else if(L2 > R1)
				start = cut1 + 1;
			else {
				if (even)
					return  (double) (Math.max(L1, L2) + Math.min(R1, R2)) / 2;
				else
					return Math.min(R1, R2);
			}
		}
		return 0;
	}
}//class end

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值