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

236 阅读2分钟

目录:算法日记

原题链接:4. 寻找两个正序数组的中位数 - 力扣(LeetCode) (leetcode-cn.com)

题目描述

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

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

数据范围

  • nums1.length==mnums1.length == m
  • nums2.length==nnums2.length == n
  • 0<=m<=10000 <= m <= 1000
  • 0<=n<=10000 <= n <= 1000
  • 1<=m+n<=20001 <= m + n <= 2000
  • 106<=nums1[i],nums2[i]<=106-10^6 <= nums1[i], nums2[i] <= 10^6

题目示例

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

算法思路

给定两个正序数组nums1nums1nums2nums2,设对应数组长度为len1len1len2len2,令len=len1+len2len = len1 + len2。求两个正序数组中位数,相当于求两个正序数组中第kk(k从1开始) 小的数,设f(k)f(k)表示两个正序数组中第kk小数值:

  • lenlen为奇数时,k=len/2+1k = \lfloor len / 2 \rfloor + 1,中位数为f(k)f(k);
  • lenlen为偶数时,k1=len/2,k2=len/2+1k_1 = len / 2, k_2 = len / 2 + 1,中位数为(f(k1)+f(k2))/2(f(k_1) + f(k_2)) / 2; 为方便讨论,令len1<len2len1 < len2nums1nums1区间[l1,r1][l1, r1]nums2nums2区间[l2,r2][l2, r2]

考虑一般情况,当len1>k/2len1 > \lfloor k / 2 \rfloorlen2>k/2len2 > \lfloor k / 2 \rfloor时,有如下三种情况:

  1. nums1[l1+k/2]<nums2[l2+k/2]nums1[l1 + \lfloor k / 2 \rfloor] < nums2[l2 + \lfloor k / 2 \rfloor],此时中位数必然不在nums1[l1,l1+k/2]nums1[l1, l1 + \lfloor k / 2 \rfloor]区间中,故该区间可舍去;
  2. nums1[l1+k/2]>nums2[l2+k/2]nums1[l1 + \lfloor k / 2 \rfloor] > nums2[l2 + \lfloor k / 2 \rfloor],此时中位数必然不在nums2[l2,l2+k/2]nums2[l2, l2 + \lfloor k / 2 \rfloor]区间中,故该区间可舍去;
  3. nums1[l1+k/2]=nums2[l2+k/2]nums1[l1 + \lfloor k / 2 \rfloor] = nums2[l2 + \lfloor k / 2 \rfloor],此时二者都为中位数,可舍弃一个,因此该情况可与上述两种情况中的任意一种合并; 每次舍去后,kk的值对应减少舍去的长度,转变为一个递归子问题。 相当于每次对nums1nums1nums2nums2中的任意一个砍半,时间复杂度满足log(n)log(n)

考虑特殊情况

  • 由于len=len1+len2,len1<len2len = len1 + len2, len1 < len2klen/2+1k ≤ \lfloor len / 2 \rfloor + 1,因此:
    • len2>k/2len2 > \lfloor k / 2 \rfloor
    • len1<k/2len1 < \lfloor k / 2 \rfloor时,为统一操作,将nums1nums1中所有元素全部取出;
    • 由于len1len1较短,合并一般情况中的1和3;
  • k=1k = 1时,按第kk小数定义,取nums1nums1nums2nums2的最小值;
  • nums1nums1先被划分完时,直接返回nums2nums2的第kk个元素;

AC代码

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number}
 */
var findMedianSortedArrays = function(nums1, nums2) {
    let len = nums1.length + nums2.length;
    if(len % 2 == 1) { 
        return find(nums1, 0, nums2, 0, (len >> 1) + 1);
    } else {
        // f(k1)
        let left = find(nums1, 0, nums2, 0, (len >> 1)); 
        // f(k2)
        let right = find(nums1, 0, nums2, 0, (len >> 1) + 1); 
        return (left + right) / 2;
    }
};

var find = function(nums1, l1, nums2, l2, k) {
    // 保证nums2比nums1长
    if(nums1.length - l1 > nums2.length - l2) return find(nums2, l2, nums1, l1, k);
    // 如果nums1已被全部舍去
    if(nums1.length === l1) return nums2[l2 + k - 1]; 
    if(k === 1) return Math.min(nums1[l1], nums2[l2]);
    // len1可能小于k
    let tl1 = Math.min(l1 + (k >> 1), nums1.length);
    let tl2 = l2 + (k >> 1);
    // 一般情况
    if(nums1[tl1 - 1] > nums2[tl2 - 1]) {
        return find(nums1, l1, nums2, l2 + (k >> 1), k - (k >> 1));
    } else {
        return find(nums1, tl1, nums2, l2, k - (tl1 - l1));
    }
}