一、问题
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的中位数 。
算法的时间复杂度应该为 O(log (m+n))
。
二、解题思路
-
中位数的定义:
- 如果两个数组的总长度为奇数,那么中位数是合并后数组的第
(m+n)/2
个元素。 - 如果总长度为偶数,中位数是合并后数组的第
(m+n)/2
和(m+n)/2 + 1
个元素的平均值。
- 如果两个数组的总长度为奇数,那么中位数是合并后数组的第
-
二分查找的优化:
- 我们可以不必真的将两个数组合并,而是通过二分查找的方法直接找到合并后的中位数。
- 通过在较小的数组中进行二分查找,可以以 O(log(min(m, n))) 的时间复杂度找到中位数。
-
关键思想:
- 我们将问题转化为寻找合并后数组中的第
k
小元素,通过二分查找不断缩小nums1
和nums2
的搜索范围。 - 每次比较
nums1
和nums2
中对应位置的元素,丢弃较小的一半元素,从而缩小问题规模。
- 我们将问题转化为寻找合并后数组中的第
三、代码
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 获取两个数组的长度
int m = nums1.length;
int n = nums2.length;
// 确保 nums1 是较短的数组,这样可以减少二分查找的范围
if (m > n) {
// 如果 nums1 比 nums2 长,交换它们,以确保 nums1 是较短的数组
return findMedianSortedArrays(nums2, nums1);
}
// 初始化二分查找的范围,low 和 high 是 nums1 中的二分查找范围
int low = 0, high = m;
// totalLeft 表示合并后的数组在分割时左侧的元素个数(加 1 是为了处理总长度为奇数的情况)
int totalLeft = (m + n + 1) / 2;
// 开始二分查找
while (low <= high) {
// i 是 nums1 的分割点
int i = (low + high) / 2;
// j 是 nums2 的分割点,保证左侧有 totalLeft 个元素
int j = totalLeft - i;
// 边界条件:如果 i == 0,表示 nums1 左侧没有元素,设为负无穷大
// 否则,取 nums1[i - 1] 作为 nums1 左侧的最大值
int nums1LeftMax = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1];
// 边界条件:如果 i == m,表示 nums1 右侧没有元素,设为正无穷大
// 否则,取 nums1[i] 作为 nums1 右侧的最小值
int nums1RightMin = (i == m) ? Integer.MAX_VALUE : nums1[i];
// 边界条件:如果 j == 0,表示 nums2 左侧没有元素,设为负无穷大
// 否则,取 nums2[j - 1] 作为 nums2 左侧的最大值
int nums2LeftMax = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1];
// 边界条件:如果 j == n,表示 nums2 右侧没有元素,设为正无穷大
// 否则,取 nums2[j] 作为 nums2 右侧的最小值
int nums2RightMin = (j == n) ? Integer.MAX_VALUE : nums2[j];
// 检查当前的分割是否正确:即左侧所有元素小于等于右侧所有元素
if (nums1LeftMax <= nums2RightMin && nums2LeftMax <= nums1RightMin) {
// 如果两个数组的总长度是奇数,中位数是左侧部分的最大值
if ((m + n) % 2 == 1) {
return Math.max(nums1LeftMax, nums2LeftMax);
}
// 如果总长度是偶数,中位数是左侧最大值和右侧最小值的平均值
else {
return (Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2.0;
}
}
// 如果 nums1LeftMax > nums2RightMin,说明 i 位置太大,应该向左移动
else if (nums1LeftMax > nums2RightMin) {
high = i - 1; // 更新二分查找范围,减少 high 值
}
// 如果 nums2LeftMax > nums1RightMin,说明 i 位置太小,应该向右移动
else {
low = i + 1; // 更新二分查找范围,增加 low 值
}
}
// 理论上不会到达这里,因为总能找到中位数
throw new IllegalArgumentException("Input arrays are not sorted.");
}
}
四、时间和空间复杂度
- 时间复杂度:
- 由于在较短的数组上进行二分查找,时间复杂度是 O(log(min(m, n)))。这是因为每次都在较短数组的范围内缩小一半的搜索区间。
- 空间复杂度:
- 空间复杂度为 O(1),因为我们只使用了常量的额外空间来存储变量。