其他分享
首页 > 其他分享> > 【解题笔记】leetcode寻找两个正序数组的中位数

【解题笔记】leetcode寻找两个正序数组的中位数

作者:互联网

文章目录

问题转化

首先,考虑只有一个有序数组的情况:寻找中位数的问题可以转化为寻找一条分割线,满足以下两个条件:

下面考虑两个有序数组,我们可以在两个数组上都划分一条分割线,这两条分割线有以下两个条件 :

解题步骤

寻找满足上述两个条件的分割线,那么我们就围绕上述两个条件来编码:为了描述方便,将nums1设置为长度较短的数组,nums2设置为长度较长的数组。

第一个条件:

要考虑奇数和偶数的情况,如果两个数组长度之和为奇数,那么我们就规定左边元素比右边元素多;如果两个数组长度之和为偶数,那么两边元素相等。由于Java的除法是向下取整(即5/2=2),因此可以讲奇偶两种情况合并,得到左边元素的总个数
t o t a l L e f t = m + n + 1 2          / / 其 中 m , n 分 别 代 表 两 个 数 组 的 长 度 totalLeft = \frac{m+n+1}{2}~~~~~~~~//其中m,n分别代表两个数组的长度 totalLeft=2m+n+1​        //其中m,n分别代表两个数组的长度

第二个条件:

要使分割线左边元素均小于右边的元素,因为两个数组均为有序数组,那么满足以下条件即可:
i为nums1分割线右边的元素,j为nums2分割线右边的元素。

nums1[i-1]<=nums2[j] && nums2[j-1]<=nums1[i]

根据上述两个条件编码:

image-20220319101709876
二分查找法编码:

//第一步:将长度最短的数组设置为nums1
if (nums2.length < nums1.length) {
  int[] temp = nums1;
  nums1 = nums2;
  nums2 = temp;
}


//第二步:设置分割线左边元素的个数
int m = nums1.length;
int n = nums2.length;
int totalLeft = (m + n + 1) / 2; //合并奇数和偶数的情况


//第三步,设置left与right,代表nums1分割线的查找区间。注:right需要设置为nums1.length,因为i可以为nums1.length,此时分割线就在nums1的最右边
int left = 0;
int right = nums1.length;

/*两个条件:
        1. 分隔线左边的元素个数等于分隔线右边的元素个素
        2. 分隔线左边的所有元素均小于分隔线右边的元素个素
        即nums1[i-1] <= nums2[j] && nums2[j-1] <= num1[i]

        注:i是nums1分割线右边的第一个元素,它的下标 = 分隔线左边元素的个数;
        j同理,因此: i + j = totalLeft,可以根据该表达式,由i确定j。
        */
while (left < right) {
  int i = left + (right - left + 1) / 2;
  int j = totalLeft - i;
  if (nums1[i - 1] > nums2[j]) {
    //说明nums1的分隔线太靠右了,需要在[left,i-1处继续寻找]
    right = i - 1;
  } else {
    //需要在[i,right]处继续寻找
    left = i;
  }
}

//第四步:分割线划分完毕,确定两个数组分割线右边的位置i,j。此时left所指向的元素是nums1分割线右边的元素
int i = left;
int j = totalLeft - i;

极端情况:

下面讨论四种分割线划分的极端情况,仅以两种情况举例说明

int nums1LeftMax = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
//此时nums1分割线左边没有元素了,因此nums1分割线左边元素的最大值要设置成无限小,在比较时直接选中nums2分割线的左边元素,其余同理
int nums1RightMin = i == m ? Integer.MAX_VALUE : nums1[i];
int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
int nums2RightMin = j == n ? Integer.MAX_VALUE : nums2[j];

得到中位数

分割线划分完毕后,即可求得中位数:

//最后一步:中位数
if ((m + n) % 2 == 0) {
  //偶数
  return (double)(Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2;
}
return Math.max(nums1LeftMax, nums2LeftMax);

注意

在写二分查找法时,如果查找到右区间时使用left=middle的方式编码,那么需要注意避免死循环的情况。
比如某个区间只有两个数[i,j],如果left=iright=j,那么若中间值一直不动,就会陷入死循环。因此确定中间值的时候应该使用left + (right - left + 1) / 2,这样就能保证如果二分查找查到了右区间,左边界加一。详情可见代码

完整代码

class Solution {
  public double findMedianSortedArrays(int[] nums1, int[] nums2) {

    //第一步:将长度最短的数组设置为nums1
    if (nums2.length < nums1.length) {
      int[] temp = nums1;
      nums1 = nums2;
      nums2 = temp;
    }


    //第二步:设置分割线左边元素的个数
    int m = nums1.length;
    int n = nums2.length;
    int totalLeft = (m + n + 1) / 2; //合并奇数和偶数的情况


    //第三步,设置left与right,代表nums1分割线的查找区间。注:right需要设置为nums1.length,因为i可以为nums1.length,此时分割线就在nums1的最右边
    int left = 0;
    int right = nums1.length;

    /*两个条件:
        1. 分隔线左边的元素个数等于分隔线右边的元素个素
        2. 分隔线左边的所有元素均小于分隔线右边的元素个素
        即nums1[i-1] <= nums2[j] && nums2[j-1] <= num1[i]

        注:i是nums1分割线右边的第一个元素,它的下标 = 分隔线左边元素的个数;
        j同理,因此: i + j = totalLeft,可以根据该表达式,由i确定j。
        */
    while (left < right) {
      int i = left + (right - left + 1) / 2;
      int j = totalLeft - i;
      if (nums1[i - 1] > nums2[j]) {
        //说明nums1的分隔线太靠右了,需要在[left,i-1处继续寻找]
        right = i - 1;
      } else {
        //需要在[i,right]处继续寻找
        left = i;
      }
    }
    //第四步:分割线划分完毕,确定两个数组分割线右边的位置i,j。此时left所指向的元素是nums1分割线右边的元素
    int i = left;
    int j = totalLeft - i;

    //第五步:确定中位数,无论是奇数还是偶数,中位数都只与两个数组分割线左边元素的最大值x 和 右边元素的最小值y 有关。
    //因为设定分割线左边元素等于右边元素,或大于一,因此中位数=x 或中位数=(x+y)/2
    int nums1LeftMax = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
    //此时nums1分割线左边没有元素了,因此nums1分割线左边元素的最大值要设置成无限小,在比较时直接选中nums2分割线的左边元素,其余同理
    int nums1RightMin = i == m ? Integer.MAX_VALUE : nums1[i];
    int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
    int nums2RightMin = j == n ? Integer.MAX_VALUE : nums2[j];

    //最后一步:中位数
    if ((m + n) % 2 == 0) {
      //偶数
      return (double)(Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2;
    }
    return Math.max(nums1LeftMax, nums2LeftMax);
  }
}

标签:正序,int,元素,中位数,nums1,分割线,leetcode,nums2,left
来源: https://blog.csdn.net/RRie1/article/details/123597901