寻找两个正序数组的中位数-leetcode

题目描述

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的 中位数

算法的时间复杂度应该为 O(log (m+n))

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

提示:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -106 <= nums1[i], nums2[i] <= 106

解法一

思路:

只需要给出两个有序数组的一个恰当的「分割线」,中位数的值就由位于这个「分割线」的两侧的数决定
如何确定「分割线」位置可以使用二分查找法。

image-20260402155802930

1

所有左侧的元素个数为int totalLeft=(nums1.length+nums2.length+1)/2;

在第一个数组里确定分割线的位置,第二个数组中分割线位置自然就确定了。分割线满足的要求是左侧元素要小于右侧元素。利用二分法在第一个数组中寻找分割线。

代码:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if(nums1.length>nums2.length){
            int[] temp = nums1;
            nums1 = nums2;
            nums2 = temp;
        }

        int m = nums1.length;
        int n = nums2.length;

        //在nums1的区间[0,m]里寻找适当的分割线,使得nums1[i-1]<=nums2[j] && nums1[j-1]<=nums1[i]
        int totalLeft=(m+n+1)/2;

        int left=0;
        int right=m;

        while (left<right){
            int i=left+(right-left+1)/2;
            int j=totalLeft-i;
            
            //二分逻辑:左边界收敛法”。当 if(nums1[i-1] > nums2[j]) 成立时,说明 i 绝对不合法,必须在左半区间查找;否则,当前的 i 可能是正确的,我们在右半区间继续尝试寻找更大的 i。当循环结束时,left 就是满足条件的最大 i。
            if(nums1[i-1]>nums2[j]){
                right=i-1;
            }else{
                left=i;
            }
        }
        int i=left;
        int j=totalLeft-i;
        int nums1LeftMax=i==0?Integer.MIN_VALUE:nums1[i-1];
        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==1){
            return Math.max(nums1LeftMax,nums2LeftMax);
        }else{
            return (double) ((Math.max(nums1LeftMax,nums2LeftMax)+Math.min(nums1RightMin,nums2RightMin)))/2;
        }
    }
}

代码执行过程:

设定数组: nums1 = [1, 2, 3, 4, 5, 6, 7] (长度 m=7) nums2 = [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] (长度 n=11)

初始步骤:

  1. nums1.length (7) 没有 > nums2.length (11),所以不交换。
  2. m = 7, n = 11.
  3. totalLeft = (m + n + 1) / 2 = (7 + 11 + 1) / 2 = 19 / 2 = 9 (整数除法)。
    • 这意味着我们目标是找到左半部分总共包含 9 个元素。
  4. left = 0, right = m = 7.

二分查找循环 (while (left < right)) 过程:

第一次循环: left = 0, right = 7

  • i = left + (right - left + 1) / 2 = 0 + (7 - 0 + 1) / 2 = 0 + 8 / 2 = 4
  • j = totalLeft - i = 9 - 4 = 5
  • 检查条件:nums1[i-1] vs nums2[j]
    • nums1[3] (即 4)
    • nums2[5] (即 13)
  • nums1[3] (4) > nums2[5] (13) (4 不大于 13)。
  • 进入 else 分支:left = i = 4
  • 循环结束时:left = 4, right = 7

第二次循环: left = 4, right = 7

  • i = left + (right - left + 1) / 2 = 4 + (7 - 4 + 1) / 2 = 4 + 4 / 2 = 6
  • j = totalLeft - i = 9 - 6 = 3
  • 检查条件:nums1[i-1] vs nums2[j]
    • nums1[5] (即 6)
    • nums2[3] (即 11)
  • nums1[5] (6) > nums2[3] (11) (6 不大于 11)。
  • 进入 else 分支:left = i = 6
  • 循环结束时:left = 6, right = 7

第三次循环: left = 6, right = 7

  • i = left + (right - left + 1) / 2 = 6 + (7 - 6 + 1) / 2 = 6 + 2 / 2 = 7
  • j = totalLeft - i = 9 - 7 = 2
  • 检查条件:nums1[i-1] vs nums2[j]
    • nums1[6] (即 7)
    • nums2[2] (即 10)
  • nums1[6] (7) > nums2[2] (10) (7 不大于 10)。
  • 进入 else 分支:left = i = 7
  • 循环结束时:left = 7, right = 7

循环结束: left = 7 不再 < right = 7,循环终止。

确定最终分割线和计算中位数:

  • 最终 i = left = 7
  • 最终 j = totalLeft - i = 9 - 7 = 2

计算四个边界值:

  • nums1LeftMax: i = 7i != 0,所以 nums1[i-1] = nums1[6] = 7
  • nums1RightMin: i = 7i == m (m=7),所以为 Integer.MAX_VALUE
  • nums2LeftMax: j = 2j != 0,所以 nums2[j-1] = nums2[1] = 9
  • nums2RightMin: j = 2j != n (n=11),所以 nums2[j] = nums2[2] = 10

判断中位数类型:

  • m + n = 7 + 11 = 18 (偶数)。
  • 所以,中位数是 (max(nums1LeftMax, nums2LeftMax) + min(nums1RightMin, nums2RightMin)) / 2.0
  • 中位数 = (Math.max(7, 9) + Math.min(Integer.MAX_VALUE, 10)) / 2.0
  • 中位数 = (9 + 10) / 2.0 = 19 / 2.0 = 9.5
posted @ 2026-04-02 16:07  狐狸胡兔  阅读(10)  评论(0)    收藏  举报