有三种方法,时间复杂度分别是O(m+n) ,O(k),O(log(m+n))
注意点:
判断合并后的数组的元素个数是奇数还是偶数
如果是奇数取中间值;如果是偶数取中间2个数的平均值。
两种求中位数的方法:
(1)方法1,判断奇数个还是偶数个
if (lengthall % 2 == 0){
result = ( all[lengthall/2-1] + all[lengthall/2] )*1.0/2;
}else{
result = all[lengthall/2];
}
(2)方法2,不需要判断奇数个还是偶数个
int l = (lengthall+1)/2-1;
int r = (lengthall+2)/2-1;
//上面减1的原因是坐标从0开始的
result = (all[l] + all[r])*1.0/2;
//这种求中位数的方法不需要判断是奇数还是偶数个
思考:
中位数相当于合并之后的top(n/2),如果是其他top数也是可以的。所以如果是一个无序数组的top k就只用小顶堆。如果是两个有序数组合并之后的top k就可以用如下的方法。
(1)O(m+n)合并两有序数组成一个新有序数组,再按中间位置取值。
两个有序数组合并为一个有序数组的时间复杂度O(m+n)。请参考https://blog.csdn.net/fkyyly/article/details/83145276
/**1
* O(m+n)
* 合并两有序数组成一个新有序数组,再按中间位置取值
* @param nums1
* @param nums2
* @return
*/
public static double findMedianSortedArrays1(int[] nums1, int[] nums2) {
int length1 = nums1.length;
int length2 = nums2.length;
int lengthall = length1 + length2;
int[] all = new int[length1+length2];
int i=0,j=0,k=0;
while (i<length1 && j <length2){
if(nums1[i]<nums2[j]){
all[k]=nums1[i];
i++;
k++;
}else{
all[k] = nums2[j];
j++;
k++;
}
}
while (i<length1){
all[k]=nums1[i];
i++;
k++;
}
while (j<length2){
all[k] = nums2[j];
j++;
k++;
}
double result;
if (lengthall % 2 == 0){
result = ( all[lengthall/2-1] + all[lengthall/2] )*1.0/2;
}else{
result = all[lengthall/2];
}
return result;
}
(2)O(k)两个指针分别从两数组开头指,比较两指针处的数,谁小谁往后移,直到两指针共移动k次,k为中间位置
/**2
* O(k)
* 两个指针分别从两数组开头指,比较两指针处的数,谁小谁往后移,
* 直到两指针共移动k次,k为中间位置
* 注意总数有奇数个还是偶数个计算中位数的方法
* @param nums1
* @param nums2
* @return
*/
public static double findMedianSortedArrays2(int[] nums1, int[] nums2) {
int length1 = nums1.length;
int length2 = nums2.length;
int lengthall = length1 + length2;
int inx1 = 0;
int inx2 = 0;
int x = -1;
int y = -1;
while (inx1 + inx2 < lengthall){
if (inx1 < length1) {
while (inx2 == length2 || nums1[inx1] <= nums2[inx2] ) {
//nums1到头了或者nums1此时的元素小于nums2此时的元素,这两种情况下从nums1取值(也就是nums1和nums2小的移动)
inx1++;
//下面是总长度是奇数还是偶数对于中位数的计算
if (inx1 + inx2 == (lengthall + 1) / 2) {
x = nums1[inx1 - 1];
}
if (inx1 + inx2 == (lengthall + 2) / 2) {
y = nums1[inx1 - 1];
return (x + y) * 1.0 / 2;
}
if (inx1 == length1){
break;
}
}
}
if (inx2 < length2){
while ( inx1 == length1 || nums2[inx2] <= nums1[inx1] ) {
inx2++;
if (inx1 + inx2 == (lengthall + 1)/2){
x = nums2[inx2-1];
}
if (inx1 + inx2 == (lengthall + 2)/2){
y = nums2[inx2-1];
return (x+y)*1.0/2;
}
if (inx2 == length2){
break;
}
}
}
}
return -1;
}
(3)O(log(m+n))两个数组分别采用二分法查找
思想:上面的是数组num1[],下面是数组num2[]。
使用递归的思想分别对num1和num2求中位数,因为是有序的,所以直接取n/2处的值即可。
(1)如果num1的中位数=num2的中位数,那么就是最终的结果。
(2)如果num1的中位数<num2的中位数,那么下次就在[B,C]和[D,E]两个新数组直接找中位数。
(3)如果num1的中位数>num2的中位数,那么下次就在[A,B]和[E,F]两个新数组直接找中位数。
(4)重复这个步骤,直到新数组的长度为2
如果数组a的中位数小于数组b的中位数,那么整体的中位数只可能出现在a的右区间加上b的左区间之中;
如果数组a的中位数大于等于数组b的中位数,那么整体的中位数只可能出现在a的左区间加上b的右区间之中。
关键就是利用分治的思想逐渐缩小a的区间和b的区间来找到中位数。
首先假设数组A和B的元素个数都大于k/2,我们比较A[k/2-1]和B[k/2-1]两个元素,这两个元素分别表示A的第k/2小的元素和B的第k/2小的元素。这两个元素比较共有三种情况:>、<和=。如果A[k/2-1]<B[k/2-1],这表示A[0]到A[k/2-1]的元素都在A和B合并之后的前k小的元素中。换句话说,A[k/2-1]不可能大于两数组合并之后的第k小值,所以我们可以将其抛弃。
当A[k/2-1]>B[k/2-1]时存在类似的结论。
当A[k/2-1]=B[k/2-1]时,我们已经找到了第k小的数,也即这个相等的元素,我们将其记为m。由于在A和B中分别有k/2-1个元素小于m,所以m即是第k小的数。(这里可能有人会有疑问,如果k为奇数,则m不是中位数。这里是进行了理想化考虑,在实际代码中略有不同,是先求k/2,然后利用k-k/2获得另一个数。)
通过上面的分析,我们即可以采用递归的方式实现寻找第k小的数。此外我们还需要考虑几个边界条件:
如果A或者B为空,则直接返回B[k-1]或者A[k-1];
如果k为1,我们只需要返回A[0]和B[0]中的较小值;
如果A[k/2-1]=B[k/2-1],返回其中一个;
https://blog.csdn.net/hjhjhx26364/article/details/80251675
https://blog.csdn.net/darminz/article/details/77500474