小white刷题记——LeetCodeHot100_4

题目:

题目思路:

        这个题,乍一看感觉题真简单啊,无非就是合并两个数组,然后判断合并后的数组长度是奇数还是偶数,然后求一下中位数就可以了。但是如果这种思路去算的话,时间复杂度是不符合题意的。

按照这个思路来的话,代码可以写成下边这样:

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小数这个方法也实现了它!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值