LeetCode - 4. 寻找两个正序数组的中位数

128 阅读3分钟

1.题目简述

给定两个大小分别为 mmnn 的的正序数组(从小到大) num1num1nums2nums2,要求返回这两个中序数组的中位数

示例1

输入:nums1 = [1,2,8,10,12], nums2 = [2,5,6,9,10,16]

输出:8.00000

说明:合并数组 = [1,2,2,5,6,8,9,10,10,12,16],中位数 8

示例2

输入:nums1 = [1,2], nums2 = [3,4]

输出:2.50000

解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

详情请查看 LeetCode 官网:4. 寻找两个正序数组的中位数 - 力扣(LeetCode)

2.方法一:二分查找

  • 假设中位数为 k,分别比较 nums1[k21]nums1[\frac{k}{2}-1]num2[k21]num2[\frac{k}{2} - 1]
    • nums1[k21]<=num2[k21]nums1[\frac{k}{2}-1] <= num2[\frac{k}{2} - 1] 时,说明比 nums1[k21]nums1[\frac{k}{2}-1] 小的元素只有 (k21)+(k21)=k2(\frac{k}{2}-1) + (\frac{k}{2}-1)=k-2,因此可知 nums1[0..k21]nums1[0..\frac{k}{2}-1] 中的元素都不是第 kk 个元素,可以直接排除
      • 排除后,需要将 kk 减去排除的元素个数,并更新 nums1nums1 的开始位置
    • nums1[k21]>num2[k21]nums1[\frac{k}{2}-1] > num2[\frac{k}{2} - 1] 时,同理

说明:该题解是官方题解,详情请查看:寻找两个有序数组的中位数 - 官方题解 - 力扣(LeetCode)

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int length1 = nums1.length,length2 = nums2.length;
    int total = length1 + length2;
    if(total%2 == 1){
        int midIndex = total/2;
        double median = getKthElement(nums1,nums2,midIndex+1);
        return median;
    }else{
        int midIndex = total/2-1,midIndex2 = total/2;
        double median = (getKthElement(nums1,nums2,midIndex+1) + getKthElement(nums1,nums2,midIndex2+1)) / 2.0;
        return median;
    }
}

public int getKthElement(int[] nums1,int[] nums2,int k){
    int length1 = nums1.length,length2 = nums2.length;
    int index1 = 0,index2 = 0;

    while(true){
        // 边界情况
        if(index1 == length1){
            return nums2[index2+k-1];
        }
        if(index2 == length2){
            return nums1[index1+k-1];
        }
        if(k == 1){
            return Math.min(nums1[index1],nums2[index2]);
        }
        
        // 正常情况
        int half = k / 2;
        int newIndex1 = Math.min(index1+half,length1) - 1;
        int newIndex2 = Math.min(index2+half,length2) - 1;
        int pivot = nums1[newIndex1],pivot2 = nums2[newIndex2];
        if(pivot <= pivot2){
            // 将 nums1[index1..newIndex1] 之间的数字删掉
            // 因为比第 k 个元素小;同时更新 k 元素,因为两个数组中的元素减小
            k -= (newIndex1-index1) + 1;
            index1 = newIndex1 + 1;
        }else{
            k -= (newIndex2-index2) + 1;
            index2 = newIndex2 + 1;
        }
    }
}

示例1 图解: 寻找两个正序数组的中位数.png

寻找两个正序数组的中位数2.png

寻找两个正序数组的中位数3.png

3.方法二:双指针

  • 定位中位数是第几个元素
  • 定义两个指针,同两个有序数组排序的处理方式相同,所指元素更小的指针向前移动
  • 在移动过程中,记录目前已排序元素的个数;当达到中位数所在位置时,记录中位数
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int m = nums1.length,n = nums2.length;
    // 是否都已找到中位数;true 表示未找到;false 表示已找到
    boolean flag = true;
    // 记录总长度
    int total = m+n;
    // 记录中位数个数
    int medianNum = total%2==1 ? 1:2;
    // 当前排序数组中的索引
    int nowNum = -1;
    // left1 是 nums1 指针;left2 是 nums2 指针
    int left1 = 0,left2 = 0;
    // 记录中位数
    double sum = 0.0;
    while(left1<m && left2<n && flag){
        int num = Math.min(nums1[left1],nums2[left2]);
        if(nums1[left1] < nums2[left2]){
            left1++;
        }else{
            left2++;
        }
        nowNum++;
        if(total%2==1 && nowNum==total/2){
            sum += num;
            flag = false;
        }else if(total%2==0 && (nowNum==total/2-1||nowNum==total/2)){
            sum += num;
            if(nowNum == total/2) flag=false;
        }
    }

    while(left1<m && flag){
        nowNum++;
        if(total%2==1 && nowNum==total/2){
            sum += nums1[left1];
            flag = false;
        }else if(total%2==0 && (nowNum==total/2-1||nowNum==total/2)){
            sum += nums1[left1];
            if(nowNum == total/2) flag=false;
        }
        left1++;
    }

    while(left2<n && flag){
        nowNum++;
        if(total%2==1 && nowNum==total/2){
            sum += nums2[left2];
            flag = false;
        }else if(total%2==0 && (nowNum==total/2-1||nowNum==total/2)){
            sum += nums2[left2];
            if(nowNum == total/2) flag=false;
        }
        left2++;
    }

    return sum/medianNum;
}