/**
* 已知两个有序数组,找到两个数组合并后的中位数。
* [1,2] [3] 中位数 2.0
* [1,3] [2,4] 中位数 2+3 / 2 = 2.5
*/
public class Test04 {
public static void main(String[] args) {
int[] nums1 = {1,3,4,9};
int[] nums2 = {1,2,3,4,5,6,7,8,9,10};
// System.out.println(findMedianSortedArrays01(nums1, nums2));
System.out.println(findMedianSortedArrays02(nums1, nums2));
System.out.println(findMedianSortedArrays03(nums1, nums2));
}
/**
* 简单粗暴,先将两个数组合并,两个有序数组的合并也是归并排序中的⼀部分。然后根据奇数,还是偶数,
* 返回中位数。
* @param nums1
* @param nums2
* @return
*/
static double findMedianSortedArrays01(int[] nums1, int[] nums2) {
int[] nums;
int m = nums1.length;
int n = nums2.length;
nums = new int[m + n];
if (m == 0) {
if (n % 2 == 0) {
//偶数数据
return (nums2[n / 2 - 1] + nums2[n / 2]) / 2.0;
} else {
return nums2[n / 2];
}
}
if (n == 0) {
if (m % 2 == 0) {
return (nums1[m / 2 - 1] + nums1[m / 2]) / 2.0;
} else {
return nums1[m / 2];
}
}
// 组合2个数组, 排序 并找出中位数
int count = 0;
int i = 0, j = 0;
while (count != (m + n)) {
if (i == m) {
while (j != n) {
nums[count++] = nums2[j++];
}
break;
}
if (j == n) {
while (i != m) {
nums[count++] = nums1[i++];
}
break;
}
// 比较2个数组 对应下标的大小
if (nums1[i] < nums2[j]) {
nums[count++] = nums1[i++];
} else {
nums[count++] = nums2[j++];
}
}
//得到的是一个有序的新数组
if (count % 2 == 0) {
return (nums[count / 2 - 1] + nums[count / 2]) / 2.0;
} else {
return nums[count / 2];
}
}
/**
* 前提是2个有序数组
* 不需要将两个数组真的合并,我们只需要找到中位数在哪⾥
* ⽤ len 表示合并后数组的⻓度,如果是奇数,我们需要知道第 (len + 1)/ 2 个数就可以了,如果遍历的话
* 需要遍历 int ( len / 2 ) + 1 次。如果是偶数,我们需要知道第 len / 2 和 len / 2 + 1 个数,也是需要遍历 len
* / 2 + 1 次。所以遍历的话,奇数和偶数都是 len / 2 + 1 次。 所以 i <= len/2 从0开始 到 len/2, 循环了 len/2+1
* 返回中位数的话,奇数需要最后⼀次遍历的结果就可以了,偶数需要最后⼀次和上⼀次遍历的结果。所以我
* 们⽤两个变量 left 和 right ,right 保存当前循环的结果,在每次循环前将 right 的值赋给 left 。
* @param nums1
* @param nums2
* @return
*
* 时间复杂度:遍历 len/2 + 1 次,len = m + n ,所以时间复杂度依旧是 O(m + n)。
* 空间复杂度:我们申请了常数个变量,也就是 m,n,len,left,right,aStart,bStart 以及 i 。
* 总共 8 个变量,所以空间复杂度是 O(1)。
*/
static double findMedianSortedArrays02(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int len = m + n;
int left = -1, right = -1; //中位数的值
int aStart = 0, bStart = 0; // 游标
// 循环 len / 2 + 1 次
for (int i = 0; i <= len / 2; i++) {
//第 len / 2 次的值
left = right;
//中位数 取 nums1的情况 bStart >= n 超过 nums2的索引界限 不用比较
if (aStart < m && (bStart >= n || nums1[aStart] < nums2[bStart])) {
right = nums1[aStart++];
} else {
right = nums2[bStart++];
}
}
// 计算中位数
if ((len & 1) == 0) {
return (left + right) / 2.0;}
else {
return right;
}
}
/**
* 题⽬的要求 O ( log ( m + n ) )。看到 log ,很明显,我们只有⽤到⼆分的⽅法才能达到。
*
* 由于数列是有序的,其实我们完全可以⼀半⼉⼀半⼉的排除。 假设我们要找第 k ⼩数,我们可以每次循环排除掉 k / 2 个数。
* 更⼀般的情况 A [ 1 ],A [ 2 ],A [ 3 ],A [ k / 2] ... ,B[ 1 ],B [ 2 ],B [ 3 ],B[ k / 2] ... ,如果 A [ k / 2 ] <
* B [ k / 2 ] ,那么 A [ 1 ],A [ 2 ],A [ 3 ],A [ k / 2] 都不可能是第 k ⼩的数字。
* @param nums1
* @param nums2
* @return
*/
static double findMedianSortedArrays03(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int left = (n + m + 1) / 2; // (14+1) / 2 =7 数组 第7,8个是中位数
int right = (n + m + 2) / 2; // 14+2 / 2 = 8
//将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。 两个中位数之和
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n -
1, nums2, 0, m - 1, right)) * 0.5;
}
/**
*
* @param nums1
* @param start1
* @param end1
* @param nums2
* @param start2
* @param end2
* @param k
* @return
*/
static 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]);
}
//游标所在位置 第 x 个数 -1
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));
}
}
}