题目
给定两个大小为 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
示例 3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:
输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:
输入:nums1 = [2], nums2 = []
输出:2.00000
方法一 归并数组
public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
int nums1Length = nums1.length;
int nums2Length = nums2.length;
int i=0;
int j = 0;//表示nums1,nums2的起始索引
int[] temp = new int[nums1Length+nums2Length];
int t = 0; //指向temp的初始索引
//遍历两个数组,依次按照顺序填充到temp里面去,
//直到有一方遍历完成
while (i<=nums1Length-1 && j<=nums2Length-1){
if(nums1[i]<=nums2[j]){
//说明nums1里面的元素小于等于nums2里面的元素
temp[t]= nums1[i];
t++;
i++;
}else{
temp[t] = nums2[j];
t++;
j++;
}
}
while (i<=nums1Length-1){
temp[t] = nums1[i];
t++;
i++;
}
while (j<=nums2Length-1){
temp[t] = nums2[j];
t++;
j++;
}
//获取temp数组的长度
int tempLength = temp.length;
double centerNum;
if(tempLength%2 == 1){
//如果数组是奇数
centerNum = (double) temp[(0+temp.length)/2];
return centerNum;
}else{
//如果数组是偶数
int mid = (0+tempLength)/2;//计算数组中值
centerNum = ((double)temp[mid]+(double) temp[mid-1])/2;
return centerNum;
}
方法二 直接for循环查找中位数
public static double findMedianSortedArrays1(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int aStart = 0; //指向nums1的初始索引
int bStart = 0; //指向nums2的初始索引
int len = m+n;
int left = -1; int right = -1;
for (int i = 0; i <= len/2; i++) {
left = right;
if(aStart<m && ( bStart>=n || nums1[aStart]<nums2[bStart] )){
right = nums1[aStart];
aStart++;
}else{
right = nums2[bStart];
bStart++;
}
}
if(len%2==1){
return right;
}else{
return ((double) (left+right))/2;
}
}
思路如下
用 len 表示合并后数组的长度,如果是奇数,我们需要知道第 (len+1)/2 个数就可以了,如果遍历的话需要遍历 int(len/2 )
- 1 次。如果是偶数,我们需要知道第 len/2和 len/2+1 个数,也是需要遍历 len/2+1 次。所以遍历的话,奇数和偶数都是 len/2+1 次。
返回中位数的话,奇数需要最后一次遍历的结果就可以了,偶数需要最后一次和上一次遍历的结果。所以我们用两个变量 left 和
right,right 保存当前循环的结果,在每次循环前将 right 的值赋给 left。这样在最后一次循环的时候,left 将得到
right 的值,也就是上一次循环的结果,接下来 right 更新为最后一次的结果。循环中该怎么写,什么时候 A 数组后移,什么时候 B 数组后移。用 aStart 和 bStart 分别表示当前指向 A 数组和 B
数组的位置。如果 aStart 还没有到最后并且此时 A 位置的数字小于 B
位置的数组,那么就可以后移了。也就是aStart<m&&A[aStart]< B[bStart]。但如果 B 数组此刻已经没有数字了,继续取数字 B[ bStart ],则会越界,所以判断下 bStart 是否大于数组长度了,这样 ||
后边的就不会执行了,也就不会导致错误了,所以增加为 aStart<m&&(bStart) >=
n||A[aStart]<B[bStart]) 。
方法三 二分查找法
//第k数解法
public static double findMedianSortedArrays2(int[] nums1, int[] nums2) {
int length1 = nums1.length, 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, midIndex2 = totalLength / 2;
double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
return median;
}
}
//找中位数(二分查找法)
public static int getKthElement(int[] nums1,int[] nums2,int k){
//获取两个数组的长度
int m = nums1.length;
int n = nums1.length;
//定义两个索引用来遍历数组
int index1 = 0;
int index2 = 0;
while (true){
//退出循环条件
if(k==1){
return Math.min(nums1[index1],nums2[index2]);
}
//如果一个数组为空,说明该数组中的所有元素都被排除了,我们可以直接返回另一个数组中第k小的元素
if(index1 == m){
return nums2[index2+k-1];
}
if(index2 == n){
return nums1[index1+k-1];
}
//为了防止数组越界
int half = k/2;
int newIndex1 = Math.min(m,index1+half)-1;
int newIndex2 = Math.min(n,index2+half)-1;
if(nums1[newIndex1]<=nums2[newIndex2]){
//更新k
k-=half;
//更新index的指向
index1=newIndex1+1;
}else{
//更新k
k-=half;
//更新index的指向
index2=newIndex2+1;
}
}
}