寻找两个正序数组的中位数
二分法
根据中位数的定义,当 m+n 是奇数时,中位数是两个有序数组中的第 (m+n)/2 个元素,当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2 个元素和第 (m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k 小的数,其中 k 为 (m+n)/2 或 (m+n)/2+1。
假设有两个正序数组A、B,要找第 k 个元素,我们可以比较 A[k/2−1] 和 B[k/2−1]。由于 A[k/2−1] 和 B[k/2−1] 的前面分别有 A[0 … k/2−2] 和 B[0 … k/2−2],即 k/2−1 个元素,对于 A[k/2−1] 和 B[k/2−1] 中的较小值,最多只会有 (k/2−1)+(k/2−1)≤k−2 个元素比它小,那么它就不能是第 k 小的数了,这k/2个数可以排除掉,k 也应该减去排除的个数。
有以下三种情况需要特殊处理:
-
如果 A[k/2−1] 或者 B[k/2−1] 越界,那么我们可以选取对应数组中的最后一个元素。在这种情况下,我们必须根据排除数的个数减少 k 的值,而不能直接将 k 减去 k/2。
-
如果一个数组为空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第 k 小的元素。
-
如果 k=1,我们只要返回两个数组首元素的最小值即可。
/**
* Question: 二分法查找两个有序数组的中位数
* @param nums1
* @param nums2
* @return
*/
public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
int totalLength = nums1.length + nums2.length;
if ((totalLength & 1) == 1){
int midIndex = totalLength / 2;
double media = getKthElement(nums1, nums2, midIndex+1);
return media;
}else {
int medIndex1 = totalLength/2 - 1, medIndex2 = medIndex1 + 1;
double media = (getKthElement(nums1, nums2, medIndex1 + 1) + getKthElement(nums1, nums2, medIndex2+1))/2.0;
return media;
}
}
public static int getKthElement(int[] nums1, int[] nums2, int k){
int len1 = nums1.length, len2 = nums2.length;
int index1 = 0, index2 = 0;
while(true){
if(index1 == len1){ //nums1 到达边界
return nums2[index2 + k -1];
}
if(index2 == len2){ //nums2 到达边界
return nums1[index1 + k -1];
}
if (k == 1){
return Math.min(nums1[index1], nums2[index2]);
}
// 正常情况
int half = k / 2;
int newIndex1 = Math.min(index1 + half, len1) - 1;
int newIndex2 = Math.min(index2 + half, len2) - 1;
int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) { // 更新 k,下标偏移
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
时间复杂度 O(log(m+n)),空间复杂度 O(1)