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

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

链接 #

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

题目 #

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

算法的时间复杂度应该为 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

解答 #

时间复杂度是 O(n) 的解法,但是简单容易记忆:

class Solution {
public:
  double slice_mid(vector<int> v, int from, int to) {
    auto size = (from - to + 1);
    auto mid = from + (to - from) / 2; // 靠下的中点
    if (size % 2 == 0) {
      return (double(v[mid]) + double(v[mid + 1])) / 2.0;
    } else {
      return v[mid];
    }
  }
  double findMedianSortedArrays(vector<int> &a, vector<int> &b) {
    if (a.size() > b.size()) {
      return findMedianSortedArrays(b, a);
    }

    int al = 0, ar = a.size() - 1;
    int bl = 0, br = b.size() - 1;

    while (true) {
      auto size_a = ar + 1 - al;
      auto size_b = br + 1 - bl;
      // 如果两个都只剩一个,则返回平均值
      if (size_a == 1 && size_b == 1) {
        return (double(a[ar]) + double(b[br])) / 2;
      }
      // 如果某个数组空,则返回另一个的中位数
      if (size_a == 0) {
        return slice_mid(b, bl, br);
      }
      if (size_b == 0) {
        return slice_mid(a, al, ar);
      }

      // 从小端删除左边
      if (a[al] <= b[bl]) {
        al++;
      } else {
        bl++;
      }
      // 从大端删除右边
      if (a[ar] > b[br]) {
        ar--;
      } else {
        br--;
      }
    }
  }
};

另一个比较好的题解 #

推荐题解:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/572881/shua-chuan-lc-po-su-jie-fa-fen-zhi-jie-f-wtu2/

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int tot = nums1.length + nums2.length;
        if (tot % 2 == 0) {
            int left = find(nums1, 0, nums2, 0, tot / 2);
            int right = find(nums1, 0, nums2, 0, tot / 2 + 1);
            return (left + right) / 2.0;
        } else {
            return find(nums1, 0, nums2, 0, tot / 2 + 1);
        }
    }

    // i和j:分别表示当前考虑的n1和n2的起始索引。
    // k:要找的第k小的元素。
    int find(int[] nums1, int i, int[] nums2, int j, int k) {
        if (nums1.length - i > nums2.length - j) return find(nums2, j, nums1, i, k);
        if (i >= nums1.length) return nums2[j + k - 1];
        if (k == 1) {
            return Math.min(nums1[i], nums2[j]);
        } else {
            int si = Math.min(i + (k / 2), nums1.length); // nums1 左半段中点
            int sj = j + k - (k / 2); // nums2 右半段中点
            if (nums1[si - 1] > nums2[sj - 1]) { // 比较的是第一段的最后一个元素和第二段的前一个元素
                return find(nums1, i, nums2, sj, k - (sj - j));
            } else {
                return find(nums1, si, nums2, j, k - (si - i));
            }
        }
    }
}

关键:为什么可以舍弃部分数组

如果A数组中的第k/2大的数字小于B数组中的第k/2大的数字,这意味着A数组中最多只有k/2个数字可能是第k小的数字。因为B数组中有k/2个数字比A数组中的所有数字都小,所以A数组中较小的k/2个数字不可能是第k小的数字,可以舍弃。

反之亦然,如果B数组中的数字较小,则可以舍弃B数组中较小的k/2个数字。