问题描述
给定两个大小为 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
- 1、由于题目要求时间复杂度为O(log(m + n)),因此要使用二分搜索
- 2、由于nums1和nums2是从小到大排好序的,因此我们要利用好这个性质
- 先处理nums1或者nums2为空的情况,这个测试用例中有,需要特别注意
- 处理nums1和nums2没有交叉的情况
- 对于nums1和nums2右交叉的情况,按照中位数的定义,我们可以对nums1和nums2按照左右分割的方式处理,如下
left_part | right_part
nums1[0], nums1[1], ..., nums1[i-1] | nums1[i], nums1[i+1], ..., nums1[m-1]
nums2[0], nums2[1], ..., nums2[j-1] | nums2[j], nums2[j+1], ..., nums2[n-1]
我们只需left_part,right_part满足以下条件即可
1) len(left_part) == len(right_part)
2) max(left_part) <= min(right_part)
这样我们可以推出i,j,m,n的关系
i+j=(m + n + 1) / 2
于是,只需要决定i,那么j自然就可以求得
求得i,j之后,再按照合并后的数组长度m+n的奇偶性来求中位数
当m+n为奇数时,中位数为max(left_part)
当m+n为偶数时,中位数为(max(left_part)+min(right_part))/2
代码实现
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length == 0 && nums2.length == 0) {
return 0;
}
if (nums1.length == 0) {
return handleNoIntersect(nums1, nums2);
}
if (nums2.length == 0) {
return handleNoIntersect(nums2, nums1);
}
//如果第一个数组的最大值比第二个数组的最小值还小
if (nums1[nums1.length - 1] <= nums2[0]) {
return handleNoIntersect(nums1, nums2);
}
//如果第二个数组的最大值比第一个数组的最小值还小
if (nums1[0] >= nums2[nums2.length - 1]) {
return handleNoIntersect(nums2, nums1);
}
//根据nums1Left来决定nums2Left,因此nums1的长度越短,算法效率越高
if (nums1.length > nums2.length) {
int[] tempNums = nums1;
nums1 = nums2;
nums2 = tempNums;
}
//nums1的左半部分的元素数目
int nums1Left = 0;
int nums1LeftLower = 0;
int nums1LeftHigh = nums1.length;
//nums2的左半部分的元素数目
int nums2Left = 0;
int totalHalfLength = (nums1.length + nums2.length + 1) / 2;
while (nums1LeftLower <= nums1LeftHigh) {
nums1Left = (nums1LeftLower + nums1LeftHigh) / 2;
nums2Left = totalHalfLength - nums1Left;
//对nums1,左边无元素,所以只用比较nums2的左边和nums1的右边
if (nums1Left == 0) {
if (nums2[nums2Left - 1] > nums1[nums1Left]) {
nums1LeftLower = nums1Left + 1;
continue;
} else {
break;
}
}
//对nums1,右边无元素,所以只用比较nums1的左边和nums2的右边
if (nums1Left == nums1.length) {
if (nums1[nums1Left - 1] > nums2[nums2Left]) {
nums1LeftHigh = nums1Left - 1;
continue;
} else {
break;
}
}
if (nums1Left >= nums1LeftLower && nums2[nums2Left - 1] > nums1[nums1Left]) {
//如果nums2的左边大于nums1的右边,说明nums1的右边取小了
nums1LeftLower = nums1Left + 1;
continue;
}
if (nums1Left <= nums1LeftHigh && nums1[nums1Left - 1] > nums2[nums2Left]) {
//如果nums1的左边大于nums2的右边,说明nums1当前的左边取大了
nums1LeftHigh = nums1Left - 1;
continue;
}
//nums1Left取的正好
break;
}
// 如果加起来是偶数个,就取左侧的最大数和右侧的最小数之和的平均值
if ((nums1.length + nums2.length) % 2 == 0) {
return (maxLeftValue(nums1Left, nums1, nums2Left, nums2) + minRightValue(nums1Left, nums1, nums2Left, nums2)) / 2.0;
} else {
// 如果加起来是奇数个,就取左侧的最大数
return maxLeftValue(nums1Left, nums1, nums2Left, nums2);
}
}
/**
* 处理两个数组不相交的情况
*/
private double handleNoIntersect(int[] leftArray, int[] rightArray) {
int leftPosition = (leftArray.length + rightArray.length + 1) / 2;
int rightPosition = (leftArray.length + rightArray.length + 2) / 2;
int leftValue;
int rightValue;
if (leftPosition <= leftArray.length) {
leftValue = leftArray[leftPosition - 1];
} else {
leftValue = rightArray[leftPosition - leftArray.length - 1];
}
if (rightPosition <= leftArray.length) {
rightValue = leftArray[rightPosition - 1];
} else {
rightValue = rightArray[rightPosition - leftArray.length - 1];
}
return ((double) leftValue + (double) rightValue) / 2;
}
private int maxLeftValue(int nums1Left, int[] nums1, int nums2Left, int[] nums2) {
if (nums1Left == 0) {
return nums2[nums2Left - 1];
}
if (nums2Left == 0) {
return nums1[nums1Left - 1];
}
return Math.max(nums1[nums1Left - 1], nums2[nums2Left - 1]);
}
private int minRightValue(int nums1Left, int[] nums1, int nums2Left, int[] nums2) {
if (nums1Left == nums1.length) {
return nums2[nums2Left];
}
if (nums2Left == nums2.length) {
return nums1[nums1Left];
}
return Math.min(nums1[nums1Left], nums2[nums2Left]);
}
- 这个题目的测试用例中对边界条件的测试比较多,边界条件一定要处理好