【刷题】找两个正序数组的中位数

137 阅读1分钟

原题链接:4.寻找两个正序数组的中位数

给定两个大小分别为 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
  • -10^6 <= nums1[i], nums2[i] <= 10^6

解题:

(视频)

这个解法的精髓在于,找到nums1nums2的分割线i, j,能满足:

nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i](这里i-1就代表了分割线左边

i,j对应各自数组的(左边最大值 + 右边最小值)/ 2,就是题目要求的中位数。

那么接下来整理一下解题步骤:

首先,对nums1符合条件的分割线位置i查询,使用二分查找,终止循环的条件为left==right

lowhigh的更新逻辑是:

  1. nums1[i-1] > nums2[j]时,说明当前分割线左边比nums2[j]大,所以需要找一个更小的数,右边界缩紧
  2. nums1[i-1] <= nums2[j]时,说明当前分割线左边值比nums2[j]小,所以可以找找看有没有更大符合条件的值,左边界缩紧

经过查询后,这个left就是nums1数组的 i分割位置

nums2j 的分割位置呢也就是(nums1.length + nums2.length)/2 - i

其次,不能忘记的是,还需要处理一些特殊情况:

  1. 如果i === 0,代表i分割线在nums1数组的最左端,

    nums1LeftMax的值需要变成Numer.MIN_VALUE (最后求解max( nums1LeftMax, nums2LeftMax)时,计算出的最大值就是nums2LeftMax)

  2. 如果i===nums1.length,代表i分割线在nums1数组的最右端, 那nums1RightMin的值需要变成Numer.Max_Value (最后求解min(nums1RightMin , nums2RightMin)时,计算出的最小值就是nums2RightMin)

剩下对j处理时的逻辑同理

最后,找到了分割线之后,可以算出中位数就是:

// 如果总长度是偶数:
( 
    max( nums1LeftMax, nums2LeftMax ) + min(nums1RightMin , nums2RightMin)
) / 2

// 如果总长度是奇数:
max( nums1LeftMax, nums2LeftMax )

下面来实现一下:

var findMedianSortedArrays = function (nums1, nums2) {
    // 首先保证 nums1是相对短的那个数组
    if(nums1.length > nums2.length){
        const tmp = nums1;
        nums1 = nums2;
        nums2 = tmp;
    }
    
    // 提前准备好基础数据
    const len1 = nums1.length,
        len2 = nums2.length,
        totalLength = len1 + len2,
        totalLeft = Math.ceil(totalLength / 2); 
    // 注意这里是ceil,如果是奇数长度数组,那么这个位置是中间靠右
    
    let left =0, right = len1;
    
    // 通过二分法,不断将left、right逼近符合 nums1[i-1] <= nums2[j] 的位置  
    while(left < right){
        const i = Math.ceil((left + right) / 2);
        const j = totalLeft - i;
        
        if(nums1[i - 1] > nums2[j]) {
            right = i - 1;
        }else{
            left = i;
        }
    }
    
    // 至此,找到了分割nums1的i位置
    const i = left;
    const k = totalLeft - i;
    
    // 处理边界情况
    const n1LeftMax = i === 0 ? Number.MIN_VALUE : nums1[i - 1];
    const n1RightMin = i === len1 ? Number.MAX_VALUE : nums1[i];
    
    const n2LeftMax = j === 0 ? Number.MIN_VALUE : nums2[j - 1];
    const n2RightMin = j === len2 ? Number.MAX_VALUE : nums2[j];
    
    // 先判断长度是奇数还是偶数
    if(totalLength % 2 === 0){
        return (
            Math.max(n1LeftMax, n2LeftMax) + Math.min(n1RightMin, n2RightMin)
        ) / 2;
    } else {
        return Math.max(n1LeftMax, n2LeftMax);
    }
}