题解
-
题意:给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
-
题解:此题主要是找到两个数组的中位数位置,不断地在两个数组中寻找合理的分割线位置。
- 中位数位置:
(m+n)
偶数:(m+n+1)/2
的左右两边,m+n
奇数:(m+n+1)/2
所处元素。对于奇数来说,就是划分成两堆数据之后,奇数长度数组的中位数就在元素多一个的那边(如9个元素,划分成5+4
,中位数就在5那一堆) - 在两个数组查找中位数:这个地方的困难在于,我们虽然知道数组内部元素的大小关系,但是并不知道不同数组中元素的大小关系。但是思路没有变,想要找到中位数,就离不开将所有元素划分成左右两堆,那么如何在两个数组中对元素进行划分成左边小右边大的情况呢?
- 首先我们要知道分割线左右两端的元素需要满足的条件
- 同一个数组的比较:因为是有序数组,因此在相同数组中,分割线左右两边的元素自然是满足左边元素小,右边元素大的条件的
- 交叉比较:但是光满足上面的条件不足以划分中位数,还需要满足
nums1
的分割线左边一定小于nums2
右边的元素nums2
的分割线左边一定小于nums1
右边的元素
- 表示两个数组中分割线的位置:知一得二。因为中位数的位置一定处于
x=(m+n+1)/2
附近,因此,两个数组的分割线位置相加就应该是x
的位置,我们使用一个元素i
记录在nums1
中分割线的位置,j
记录在nums2
中分割线的位置,那么j=x-i
- 那么要如何找到合适的分割线位置呢
- 因为有知一得二的性质,我们可以通过在
nums1
中分割线,定位nums2
中的分割线,那么我们只需要在nums1
中利用二分查找找到合适的分割线即可。 - 每次更新分割线,就让
nums1[i-1]
和nums2[j]
进行比较,也就是nums1
分割线左边第一个元素和nums2
分割线右边第一个元素的比较。- 如果满足条件,说明当前元素满足左堆元素的要求,就加入到左堆中,然后从
[i,right]
继续找 - 如果不满足条件,则说明当前元素太大, 要将范围缩小为
[left, i-1]
。 - 注意,这里甚至都不需要使用
nums2
中的左边元素与nums1
中的右边元素进行对比,nums1
分割线满足条件之后,nums2
一定也会满足,这两个是充要条件。
- 如果满足条件,说明当前元素满足左堆元素的要求,就加入到左堆中,然后从
- 因为有知一得二的性质,我们可以通过在
- 边界条件:要注意,当一个数组里面的元素完全小于另一个数组的元素的时候,那么此时会出现
i==m
或者i==0
的情况,那么此时对应的nums[i]
和nums[i-1]
会出现越界的情况,因此实现的时候要注意,对于第一种情况,我们使用左边元素i-1
进行比较,因此nums[i-1]
不会出现越界,对于第二种情况,二分使用(right+left+1)/2
可以避免,因为存在两个元素的时候,永远选择的都是右边的。但是最后的时候还是需要进行一个特判,因为此时是需要考虑右边的元素(偶数长度数组的中位数),此外,虽然二分查找的时候i
不会取到0,但是可能直接不进入二分查找的过程,比如第一个数组是空的,此时i=left=0
- 中位数位置:
-
实现: 二分查找长度短的数组可以提高效率
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if(nums1.length > nums2.length){
int []tmp = nums1;
nums1 = nums2;
nums2 = tmp;
}
int m = nums1.length, n = nums2.length;
int left = 0, right = m;
int mid = (m+n+1)/2;
while(left < right){
int i = left + (right-left+1)/2;
int j = mid-i;
if(nums1[i-1] > nums2[j]){
right = i-1;
}
else{
left = i;
}
}
// 边界处理, i是分割线, 但是要注意i==0 和 i==m的情况
int i = left;
int j = mid - i;
int nums1left = (i==0)?Integer.MIN_VALUE:nums1[i-1];
int nums1right = (i==m)?Integer.MAX_VALUE:nums1[i];
int nums2left = (j==0)?Integer.MIN_VALUE:nums2[j-1];
int nums2right = (j==n)?Integer.MAX_VALUE:nums2[j];
if((m+n)%2 == 1) return Math.max(nums1left, nums2left);
else return (double)(Math.max(nums1left, nums2left) + Math.min(nums1right, nums2right))/2;
}
}