LeetCode 04寻找两个正序数组的中位数(困难)二分法

简介: 给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。你可以假设 nums1 和 nums2 不会同时为空。

题目描述:


给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。

请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。


示例 1:

nums1 = [1, 3]

nums2 = [2]

则中位数是 2.0


示例 2:

nums1 = [1, 2]

nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5


归并法(O(m+n))



分析之前小吐槽一句,这题自己真的没想到O(log(m+n))的方法,只能想到O(m+n)的归并,没想到怎么去使用二分,后来看了题解也是才明白。但也算自己理解了和大家分享一下。


对于这个问题或许本身不难,但是可能难在O(log(m+n))的时间复杂度上。


如果真的无法想到好的方法,先想着过关,该用什么方法呢?


法一暴力法:


可以将两个数组添加到一个总的数组中,然后给这个数组进行排序。正常的排序是**O(nlogn)**的时间复杂度。排序之后根据奇数偶数取中位数即可。


法二归并法:

给的两个数组本身是有序的,想必熟悉归并排序的朋友们应该能一下就想出来这个方法,两个有序的.只需按照以下流程即可完成归并:

  • 待归并的两个数组分别设置两个指针leftindex,rightindex均从0开始。新数组也设置游标index。
  • 比较两数组leftindex和rightindex位置的值,较小的那个赋值到新数组中同时新数组游标和较小的那个游标均加一。


20200809175822685.png


重复到其中一个数组遍历完,另一个数组剩余值直接加入后面即可。


20200809180028805.png


实现代码:


public double findMedianSortedArrays(int[] nums1, int[] nums2) {
     int a[]=new int[nums1.length+nums2.length];
     int lindex=0,rindex=0;
     int index=0;
     while (lindex<nums1.length&&rindex<nums2.length) {
      if(nums1[lindex]<nums2[rindex])
      {
        a[index++]=nums1[lindex++];
      }
      else {
        a[index++]=nums2[rindex++];
      }
    }
     while (lindex<nums1.length) {
        a[index++]=nums1[lindex++];
    }
     while (rindex<nums2.length) {
        a[index++]=nums2[rindex++];
    }
     if(a.length%2==0)
       return (double)(a[a.length/2-1]+a[a.length/2])/2;
     else {
      return a[a.length/2];
    }
   }


二分法(O(log(m+n)))



想到有序的,又是O(log(m+n))的时间复杂度,估计大部分人都能想到二分,我当时也是一样,但是该怎么想呢这就是一个问题。记录下我当初错误的想法:


二分,二分找到两个中间的。然后正常有个长的,有个短的,根据两个数值比较分类推测中位数应该在哪个区间……然后大脑就断电了。


对于中位数的简单分析


  • 如果两个数组长度和为奇数,那么最终这个中位数是由一位数确定的。
  • 如果两个数组长度和为偶数,那么最终这个中位数是由两位数取平均值确定的。


对两个数组的简单分析


  • 两个数组应该有一个长一点,另一个点一点(等长也不影响)。
  • 中位数可能让两个数组都分成两部分:一部分小于中位数,一部分大于中位数。但两个部分合起来总数量应该一致


20200809195634325.png


对两数组和中位数位置分析


  • 我们知道两数组虽然可能等长(不影响),但正常情况应该是一个长(m)一个短(n)。长短数组分别对应的坐标m1和n1和中位数坐标有什么关系?


  • 无论总和奇数偶数,都满足(m1+n1)=(m+n)/2;因为两个数组都是有序的所以总共小于中位数的占一半。其中m和n是定值。也就是不管你怎么变动,这两个坐标编号是总和为定值得


如何分析为定值得坐标


既然两个坐标的总和为定值,那么可不可以把其中一个当为自变量,一个看成自变量呢?比如x+y=5你不好分析但是y=5-x,你分析x同时y就确定了。对吧?

那么选择长的那个作为变量还是短的那个作为变量呢?短的,为啥,主要因为如果从长的当成变量咱们有些区域无法对应到短的(因为长度即使加上短的所有也到不了一半,处理起来麻烦):


202008092016389.png


但是短的就可以很好避免这种情况:


20200809202452204.png


所以我们就用二分去查找小的这个区间,找到最终的结果,你可能会问:什么样情况能够满足确定这条线的附近就是产生中位数的


  • 二分进行查找编号的时候,满足左侧都比线右侧小才行。这种情况在二分查找就是一个平衡的结果。


20200809203823189.png


最后找到这个index线了。取值比较你还要有注意的地方


  • 取左侧的时候左侧如果有index为0,取右侧的时候index为最大值:


20200809204559299.png


  • 所以这种在你最后取值的时候,需要考虑左右侧是否有值。同时取长的那个也要比较,因为可能出现等长情况例如:1 2 3 4,和5 6 7 8这种去到临界。需要判断当然在实现过程用三目运算简化!


总的来说:


  • 根据短的进行二分查找位置,先找到线index,说明中位数在附近产生。(奇数偶数在查找因为要除2可以通用表达式)
  • 如果总个数奇数,那么就是线左侧最大的那个(两个比较或只有一个)
  • 如果总个数偶数,那么就是线左侧最大的那个(两个比较或只有一个)和线右侧最小的那个(两个比较或只有一个)的值取平均,注意是double类型
  • 其他注意点,搞清index从0开始,搞清逻辑上的第几个和数组显示使用的第几个的index的区别


附上代码:


public static double findMedianSortedArrays2(int[] nums1, int[] nums2) {
  if(nums1.length>nums2.length)//保证num1长度小,如果不小我交换一下
  {
    int team[]=nums2.clone();
    nums2=nums1;
    nums1=team;
  } 
   int k=(nums1.length+nums2.length+1)/2;//理论中位数满足的位置
   int left=0,right=nums1.length;//二分查找短的
   while (left<right) {//找到对应位置
    int m1=(left+right)/2;//在短的位置
    int m2=k-m1;//在长的第几个
    //System.out.println(m1+" "+m2);
    if(nums1[m1]<nums2[m2-1])//left右移
      left=m1+1;
    else {//right左移
      right=m1;
    }
  }
  //System.out.println(left+" "+k);
  //左侧最大和右侧最小那个先算出来再说,根据奇偶再使用
  double leftbig= Math.max(left==0?Integer.MIN_VALUE:nums1[left-1], k-left==0?Integer.MIN_VALUE:nums2[k-left-1]);
  double rightsmall=Math.min(left==nums1.length?Integer.MAX_VALUE:nums1[left],k-left==nums2.length?Integer.MAX_VALUE:nums2[k-left]);
  //System.out.println(rightsmall);
  if((nums1.length+nums2.length)%2==0)
  {
    return (leftbig+rightsmall)/2;
  }
  else {
    return leftbig;
  }   
 }


结语



本次打卡结束,再接再励。


至于其他方法暂时先不学了,感觉这题还是挺有难度的,需要搞明白要点时候。

最后我请你们两连事帮忙一下:


  1. 点赞、关注一下支持, 您的肯定是我在平台创作的源源动力。
  2. 微信搜索「bigsai」,关注我的公众号,不仅免费送你电子书,我还会第一时间在公众号分享知识技术。加我还可拉你进力扣打卡群一起打卡LeetCode。


目录
相关文章
|
4月前
|
Go
【LeetCode 热题100】DP 实战进阶:最长递增子序列、乘积最大子数组、分割等和子集(力扣300 / 152/ 416 )(Go语言版)
本文深入解析三道经典的动态规划问题:**最长递增子序列(LIS)**、**乘积最大子数组** 和 **分割等和子集**。 - **300. LIS** 通过 `dp[i]` 表示以第 `i` 个元素结尾的最长递增子序列长度,支持 O(n²) 动态规划与 O(n log n) 的二分优化。 - **152. 乘积最大子数组** 利用正负数特性,同时维护最大值与最小值的状态转移方程。 - **416. 分割等和子集** 转化为 0-1 背包问题,通过布尔型 DP 实现子集和判断。 总结对比了三题的状态定义与解法技巧,并延伸至相关变种问题,助你掌握动态规划的核心思想与灵活应用!
157 1
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
118 0
LeetCode第53题最大子数组和
LeetCode第53题"最大子数组和"的解题方法,利用动态规划思想,通过一次遍历数组,维护到当前元素为止的最大子数组和,有效避免了复杂度更高的暴力解法。
LeetCode第53题最大子数组和
|
存储 Java API
LeetCode------合并两个有序数组(4)【数组】
这篇文章介绍了LeetCode上的"合并两个有序数组"问题,并提供了三种解法:第一种是使用Java的Arrays.sort()方法直接对合并后的数组进行排序;第二种是使用辅助数组和双指针技术进行合并;第三种则是从后向前的双指针方法,避免了使用额外的辅助数组。
LeetCode------合并两个有序数组(4)【数组】
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
321 2
LeetCode------找到所有数组中消失的数字(6)【数组】
这篇文章介绍了LeetCode上的"找到所有数组中消失的数字"问题,提供了一种解法,通过两次遍历来找出所有未在数组中出现的数字:第一次遍历将数组中的每个数字对应位置的值增加数组长度,第二次遍历找出所有未被增加的数字,即缺失的数字。
|
前端开发
LeetCode------移动零(5)【数组】
这篇文章介绍了LeetCode上的"移动零"问题,提出了一种使用双指针的原地操作解法,该方法首先将非零元素移动到数组前端并保持相对顺序,然后填充后续位置为零,以达到题目要求。
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
93 4
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
101 0
Leetcode第三十三题(搜索旋转排序数组)
LeetCode第81题搜索旋转排序数组 II
文章讲解了LeetCode第81题"搜索旋转排序数组 II"的解法,通过二分查找算法并加入去重逻辑来解决在旋转且含有重复元素的数组中搜索特定值的问题。
LeetCode第81题搜索旋转排序数组 II