给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 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
-10^6 <= nums1[i], nums2[i] <= 10^6
思路1:
老暴力拉。合并数组,排序好再取中位数。(空间复杂度O(m+n),时间复杂度O(nlogn),不排序,可优化为遍历数组的时候排好顺序 空间复杂度也得O(m+n))
public static double findMedianSortedArrays1(int[] nums1, int[] nums2) {
int[] nums = Arrays.copyOf(nums1, nums1.length + nums2.length);
System.arraycopy(nums2, 0, nums, nums1.length, nums2.length);
Arrays.sort(nums);
int length = nums.length;
if (length%2 == 0){
System.out.println(nums[length/2]);
System.out.println(nums[length/2 -1]);
return (nums[length/2] + nums[length/2 -1])/2.0;
}
return nums[length/2];
}
思路2:
已经排好序的数组以及时间复杂度log,明显提示使用二分法。
两个有序数组,要求找到两个有序数组的中位数,最直观的思路有以下两种:
使用归并的方式,合并两个有序数组,得到一个大的有序数组。大的有序数组的中间位置的元素,即为中位数。
不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是已知的。维护两个指针,初始时分别指向两个数组的下标 0 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。
第一思路的时间复杂度时O(m+n),空间复杂度时O(m+n).
第一思路的时间复杂度时O(m+n),空间复杂度时O(1).
public static double findMedianSortedArrays2(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
int len = m+n;
int left = -1, right = -1;//初始化中位两个元素为-1
int startA = 0, startB = 0;//两个数组的指针
for (int i = 0; i <=len/2 ; i++) {//至多len/2+1次偏移
left = right;
if (startA<m && (startB>=n || nums1[startA] < nums2[startB])){
right = nums1[startA++];//指针A小于数组1的总长度且数组1对应的元素小于数组2的时候 偏移指针A(限制条件:指针B未超长)
}else {
right = nums2[startB++];
}
}
if ((len&1) ==0){//位运算 m+n为偶数时
return (left + right) / 2.0;
}else {
return right;
}
}
思路3:
题目要求时间复杂度为O(log(m+n)).
根据中位数的定义,当 m+n是奇数时,中位数是两个有序数组中的第 (m+n)/2 个元素,当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2(个元素和第 (m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k 小的数,其中 k 为 (m+n)/2或 (m+n)/2+1.因为数组是排好序的,我们可以通过二分查找,每次筛去k/2个。
无论是找第奇数个还是第偶数个数字,对我们的算法并没有影响,而且在算法进行中,k 的值都有可能从奇数变为偶数,最终都会变为 1 或者由于一个数组空了,直接返回结果。
所以我们采用递归的思路,为了防止数组长度小于 k/2,所以每次比较 min(k/2,len(数组) 对应的数字,把小的那个对应的数组的数字排除,将两个新数组进入递归,并且 k 要减去排除的数字的个数。递归出口就是当 k=1 或者其中一个数字长度是 0 了.
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int left = (n + m + 1) / 2;
int right = (n + m + 2) / 2;
//将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;
}
private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
int len1 = end1 - start1 + 1;
int len2 = end2 - start2 + 1;
//让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1
if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
if (len1 == 0) return nums2[start2 + k - 1];
if (k == 1) return Math.min(nums1[start1], nums2[start2]);
int i = start1 + Math.min(len1, k / 2) - 1;
int j = start2 + Math.min(len2, k / 2) - 1;
if (nums1[i] > nums2[j]) {
return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
}
else {
return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
}
}