O(log)的复杂度下找到两个有序数组的中位数

问题

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。 找出这两个有序数组的中位数,并且要求算法的时间复杂度为O(log(m + n))

可以假设 nums1 和 nums2 不会同时为空。

示例 1:

nums1 = [1, 3] nums2 = [2]

则中位数是 2.0

示例 2:

nums1 = [1, 2] nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5

思路

看到 O(log),很明显,只有用到二分法才能达到.

求两个有序数组求中位数,问题一般化为:

求两个有序数组的第k个数,当k = (m+n)/2时为原问题的解。

怎么求第k个数?

分别求出第一个和第二个数组的第 k / 2个数 a 和 b,

然后比较 a 和 b,当a < b ,

说明第 k 个数位于 a数组的第 k / 2个数后半段,或者b数组的 第 k / 2 个数前半段,

问题规模缩小了一半,然后递归处理就行。

可以达到时间复杂度是 O(log(m+n))

代码

fun findMedianSortedArrays(nums1: IntArray, nums2: IntArray): Double {
  val n = nums1.size
  val m = nums2.size
  val left = (n + m + 1) / 2
  val right = (n + m + 2) / 2

  //将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。
  return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left)
      + getKth(nums1, 0, n - 1, nums2, 0, m - 1,
      right)) * 0.5
}

//求有序数组nums1和nums2的第k小数
fun getKth(
  nums1: IntArray,
  start1: Int,
  end1: Int,
  nums2: IntArray,
  start2: Int,
  end2: Int,
  k: Int /* 第k小*/
): Int {

  val len1 = end1 - start1 + 1
  val len2 = end2 - start2 + 1
  //让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1 
  if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k)
  if (len1 == 0) return nums2[start2 + k - 1]

  // k == 1时,直接取两边元素比较小的那个就ok
  if (k == 1) return min(nums1[start1], nums2[start2])

  // 需要比较的元素 A[k/2] 和 B[k/2]
  val i = start1 + min(len1, k / 2) - 1
  val j = start2 + min(len2, k / 2) - 1

  // 元素小的一方需要丢弃 k/2 之前的元素
  return if (nums1[i] > nums2[j]) {
    getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1))
  } else {
    getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1))
  }
}

fun main() {
  val nums1: IntArray = intArrayOf(3,4,5,7)
  val nums2: IntArray = intArrayOf(8,9,20,21)
  val findMedianSortedArrays = findMedianSortedArrays(nums2, nums1)
  println(findMedianSortedArrays)
}