1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length == 0 && nums2.length == 0) {
return 0.0;
}
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> Integer.compare(b, a));
for (int i = 0; i < nums1.length; i++) {
addNum(nums1[i], minHeap, maxHeap);
}
for (int i = 0; i < nums2.length; i++) {
addNum(nums2[i], minHeap, maxHeap);
}
return minHeap.size() == maxHeap.size() ? (minHeap.peek() + maxHeap.peek()) * 0.5 : (double) minHeap.peek();
}

private void addNum(int num, PriorityQueue<Integer> minHeap, PriorityQueue<Integer> maxHeap) {
minHeap.offer(num);
maxHeap.offer(minHeap.poll());
if (maxHeap.size() > minHeap.size()) {
minHeap.offer(maxHeap.poll());
}
}
}

Two heaps. 当练手了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
int left = 0, right = nums1.length, firstHalfLength = (nums1.length + nums2.length + 1) / 2;
double ans = 0;
while (left <= right) {
int partitionX = left + (right - left) / 2, partitionY = firstHalfLength - partitionX;
int maxLeftX = partitionX == 0 ? Integer.MIN_VALUE : nums1[partitionX - 1];
int minRightX = partitionX == nums1.length ? Integer.MAX_VALUE : nums1[partitionX];
int maxLeftY = partitionY == 0 ? Integer.MIN_VALUE : nums2[partitionY - 1];
int minRightY = partitionY == nums2.length ? Integer.MAX_VALUE : nums2[partitionY];
if (maxLeftX > minRightY) {
right = partitionX - 1;
} else if (maxLeftY > minRightX) {
left = partitionX + 1;
} else {
if ((nums1.length + nums2.length) % 2 == 0) {
ans = 0.5 * (Math.max(maxLeftX, maxLeftY) + Math.min(minRightX, minRightY));
} else {
ans = (double) Math.max(maxLeftX, maxLeftY);
}
break;
}
}
return ans;
}
}

这才是正规解法.
大致的思路就是我们要把nums1和nums2都划分成两部分, 这样nums1前半部分和nums2前半部分加起来是二者总长度的一半. 如果总长度是奇数个, 那么我们让nums1和nums2前一半是二者长度一半 + 1. 这也就是第六行为什么让firstHalfLength等于二者长度 + 1除以2了, 因为即使二者长度是偶数, 我们也是保证nums1前一半 + nums2前一半是二者总长度的一半.

此时的问题就是如何划分, 这就到binary search登场了. 我们由于知道nums1和nums2前一半加在一起是个定值, 那么我们就先划分nums1, 之后nums2的划分也就自然确定下来了. 那么如何知道我们划分好了呢? 我们要保证nums1前一半的最后一个元素要小于nums2后一半第一个元素, 同样地, nums2的前一半的最后一个元素要小于nums1后一半的第一个元素. 这样我们就能保证nums1和nums2各自的前一半组成的部分就是nums1和nums2 merge后的前一半.

此时如果nums1.length + nums2.length是奇数. 那么我们就看nums1和nums2前一半最后一个数字哪个大, 大的那个就是median; 如果是偶数, 那就要看nums1和nums2前一半的最后一个数哪个大, nums1和nums2的后一半第一个数哪个小, 得出的这两个结果求平均值就是答案.

此时有个问题, 如果nums1或者nums2没有前一半, 也就是可能nums1的数字都贼大, 前一半都在nums2里面, 反之类似地道理. 或者nums1没有后一半, 那也就是nums1的数字都用上来贡献了.

因此这两种情况我们需要考虑. 上面的left和right表示可以用来分割的点有哪些, 取mid后表示mid左边是被包含进nums1前一半的元素, 此时注意是不包含mid这个元素的. 因为我们如果要包含进所有的划分方式, 必须考虑到一个也不包含的情况. 那么如果我们规定mid是被包含进划分的partition中的, 这个一个也不包含的情况就会被遗漏. 因此left和right框起来的区域表示nums1后一半可以开始的地方.

于是当我们发现paritionX是0的时候, 这表示, nums1前一半没东西, 那么根据我们上面的逻辑, 我们可能要取nums1前一半和nums2前一半的最大值, 而此时我们nums1没东西, 那我们希望的就是取nums2的前一半的最大值即可, 于是我们手动让nums1前一半最小值设置为Integer.MAX_VALUE. 如果partitionX是nums1.length, 那说明nums1全部取走了, 此时nums1没有后一半, 根据我们上面的逻辑, 我们可能要取nums1和nums2后一半的最小值, 此时那就是直接取nums2后一半的最小值即可. 那么我们为了满足这个逻辑, 让nums1后一半的最小值是Integer.MIN_VALUE即可, 这样到时候比较的时候就会取nums2的后一半第一个元素.

对于nums2的操作是一样的, 判断它有没有前一半后一半, 这里就不再赘述.

时间复杂度: O(log(m + n))
空间复杂度: O(1)