给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O ( l o g ( m + n ) ) O(log (m+n)) O(log(m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
− 1 0 6 -10^6 −106 <= nums1[i], nums2[i] <= 1 0 6 10^6 106
由于题目是有序数组,所以可以采用双指针遍历的方法。 l 1 l1 l1代表 n u m s 1 nums1 nums1数组指针索引, l 2 l2 l2代表 n u m s 2 nums2 nums2数组指针索引, m i d I n d e x midIndex midIndex代表中位数索引位置,当 n u m s [ l 1 ] nums[l1] nums[l1]<= n u m s [ l 2 ] nums[l2] nums[l2], l 1 l1 l1往右移动;当 n u m s [ l 1 ] nums[l1] nums[l1]> n u m s [ l 2 ] nums[l2] nums[l2], l 2 l2 l2往右移动;直到指针移动次数移动到中位数索引位置时,取 n u m s [ l 1 ] nums[l1] nums[l1]和 n u m s [ l 2 ] nums[l2] nums[l2]中的最小值即为中位数。
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int length1 = nums1.length;
int length2 = nums2.length;
int totalLength = length1 + length2;
if (totalLength % 2 == 1) {
int midIndex = totalLength / 2;
return findMedianNumber(nums1, nums2, midIndex);
} else {
int midIndex1 = totalLength / 2 - 1;
int midIndex2 = totalLength / 2;
double mediation = (findMedianNumber(nums1, nums2, midIndex1) + findMedianNumber(nums1, nums2, midIndex2)) / 2.0;
return mediation;
}
}
public double findMedianNumber(int[] nums1, int[] nums2, int midIndex) {
int length1 = nums1.length;
int length2 = nums2.length;
int l1 = 0;
int l2 = 0;
int index = 0;
while(l1 < length1 && l2 < length2) {
if(index == midIndex) {
return Math.min(nums1[l1], nums2[l2]);
}
if(nums1[l1] <= nums2[l2]) {
l1++;
} else {
l2++;
}
index++;
}
if(l1 == length1) {
return nums2[l2 + midIndex - index];
} else {
return nums1[l1 + midIndex - index];
}
}
时间复杂度: O ( M + N ) O(M+N) O(M+N)
空间复杂度: O ( 1 ) O(1) O(1)
可以看出,方法一中,我们寻找中位数时,是通过两个数组从左到右依次遍历的方式累加查找次数,寻找中位数,这样我们总共需要查找(m + n) / 2 + 1次,那么有没有更快的方式缩短查找次数呢?答案是每次采用二分查找,排除更多的元素,进一步缩短查找次数。
寻找中位数,我们可以理解为当两个数组长度之和为奇数时,查找第(m + n) / 2 + 1大的元素;或者当两个数组长度之和为偶数时,查找第(m + n) / 2 大的元素和(m + n) / 2 + 1大的元素,然后取平均值。这样题目就可以转化为寻找数组中第k小的数。
假设两个有序数组分别是 A A A 和 B B B。要找到第 k 个元素,我们可以比较 A [ k / 2 − 1 ] A[k/2−1] A[k/2−1]和 B [ k / 2 − 1 ] B[k/2−1] B[k/2−1]。由于 A [ k / 2 − 1 ] A[k/2−1] A[k/2−1] 和 B [ k / 2 − 1 ] B[k/2−1] B[k/2−1]的前面分别有 A [ 0 . . k / 2 − 2 ] A[0 .. k/2−2] A[0 .. k/2−2]和 B [ 0 . . k / 2 − 2 ] B[0 .. k/2−2] B[0 .. k/2−2],即 k / 2 − 1 k/2−1 k/2−1个元素,对于 A [ k / 2 − 1 ] A[k/2−1] A[k/2−1]和 B [ k / 2 − 1 ] B[k/2−1] B[k/2−1] 中的较小值,最多只会有 ( k / 2 − 1 ) + ( k / 2 − 1 ) = k − 2 (k/2−1)+(k/2−1) = k−2 (k/2−1)+(k/2−1)=k−2 个元素比它小,则 A [ k / 2 − 1 ] A[k/2−1] A[k/2−1]和 B [ k / 2 − 1 ] B[k/2−1] B[k/2−1]中的较小值最大为第 k − 1 k-1 k−1小的数,那么它就不是第 k k k小的数了。
因此我们可以归纳出二种情况:
如果 A [ k / 2 − 1 ] < = B [ k / 2 − 1 ] A[k/2−1]<=B[k/2−1] A[k/2−1]<=B[k/2−1],则比 A [ k / 2 − 1 ] A[k/2−1] A[k/2−1]小的数最多只有 A A A的前 k / 2 − 1 k/2−1 k/2−1个数和 B B B 的前 k / 2 − 1 k/2−1 k/2−1 个数,即比 A [ k / 2 − 1 ] A[k/2−1] A[k/2−1] 小的数最多只有 k − 2 k−2 k−2 个,因此 A [ k / 2 − 1 ] A[k/2−1] A[k/2−1]最大是第 k − 1 k-1 k−1小的数,不可能是第 k k k个数, A [ 0 ] A[0] A[0] 到 A [ k / 2 − 1 ] A[k/2−1] A[k/2−1]也都不可能是第 k k k 个数,可以全部排除。
如果 A [ k / 2 − 1 ] > B [ k / 2 − 1 ] A[k/2−1]>B[k/2−1] A[k/2−1]>B[k/2−1],则可以排除 B [ 0 ] B[0] B[0] 到 B [ k / 2 − 1 ] B[k/2−1] B[k/2−1]的元素。
可以看到,比较 A [ k / 2 − 1 ] A[k/2−1] A[k/2−1]和 B [ k / 2 − 1 ] B[k/2−1] B[k/2−1] 之后,可以排除 k / 2 k/2 k/2个不可能是第 k k k小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 k k k 的值,这是因为我们排除的数都不大于第 k k k小的数。当 k k k的值减小为1时,取 A A A和 B B B数组当前索引位置的较小值即为第k小的数。
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int length1 = nums1.length;
int length2 = nums2.length;
int totalLength = length1 + length2;
if (totalLength % 2 == 1) {
int midIndex = totalLength / 2;
double median = getKthElement(nums1, nums2, midIndex + 1);
return median;
} else {
int midIndex1 = totalLength / 2 - 1;
int midIndex2 = totalLength / 2;
double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
return median;
}
}
public int getKthElement(int[] nums1, int[] nums2, int k) {
/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
* 这里的 "/" 表示整除
* nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
* nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
* 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
* 这样 pivot 本身最大也只能是第 k-1 小的元素
* 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
* 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
* 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
*/
int length1 = nums1.length;
int length2 = nums2.length;
int index1 = 0;
int index2 = 0;
while (true) {
// 边界情况
if (index1 == length1) {
return nums2[index2 + k - 1];
}
if (index2 == length2) {
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, length1) - 1;
int newIndex2 = Math.min(index2 + half, length2) - 1;
int pivot1 = nums1[newIndex1];
int pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
时间复杂度: O ( l o g ( M + N ) ) O(log (M+N)) O(log(M+N))
空间复杂度: O ( 1 ) O(1) O(1)