题目:
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
二分解决方法
题目要求将时间复杂度降低到O(log(m+n)),如果对时间复杂度的要求有log,通常都需要用到二分查找,这道题也是通过二分查找实现
根据中位数的定义,当m+n是奇数的时候,中位数是两个有序数组中的第(m+n)/2个元素,当m+n是偶数的时候,中位数是两个有序数组中的第(m+n)/2和第(m+n)/2+1个元素的平均值。因此,这道题可以转换为寻找两个有序数组中的第k小的数,其中k为(m+n)/2或者(m+n)/2+1
假设有序数组分别是A和B要找到第k个元素,我们可以比较A[k/2-1]和B[k/2-1],其中/标识除法符号。由于A[k/2-1]和B[k/2-1]的前面分别有A[0 … k/2-2]和B[0 … k/2-2],即k/2-1个元素,对于A[k/2-1]和B[k/2-1]中的较小值,最多只有(k/2-1)+(k/2-1) <=k-2个元素比他小,那么他就不是第k个小的数
因此我们可以归纳出三种情况
- 如果A[k/2-1]<B[k/2-1],则比A[k/2-1]小的数最多只有k-2个,因此A[k/2-1]不可能是第k个数,A[0]到A[k/2-1]也不可能都不可能是第k个数,可以全部排除
- 如果A[k/2-1]>B[k/2-1],则可以排除B[0 …k/2-1]
- 如果A[k/2-1]=B[A/2-1],则可以归入第一种情况处理
可以看到,比较A[k/2-1]和B[k/2-1]之后可以排除k/2个可能是第k小的数,查找范围小了一半.同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除的个数,减小k的值,这是因为我们排除的数都是不大于第k小的数字
有以下三种情况需要特殊处理
-
如果A[k/2-1]或者B[k/2-1]越界,那么我们可以选取对应数组中最后的一个元素.在这种情况下,我们必须根据排除数的个数减少k的值,而不能直接将k减去k/2
-
如果一个数组是空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第K小的元素.(当前表头索引+k)
-
如果k=1,我们只需要返回数组首元素的最小值即可
-
实现代码
package 题库.寻找两个正序数组中位数;
public class Solution {
public static void main(String[] args) {
int []num1={1,2,3,4};
int []num2={2,3,4,5};
Solution solution = new Solution();
System.out.println(solution.findMedianSortedArrays(num1,num2));
}
/**
* 在两个正向数组中获得中位数
* @param num1
* @param num2
* @return
*/
private double findMedianSortedArrays(int[] num1, int[] num2) {
//获取两个数组的长度
int alen = num1.length, blen = num2.length;
//获取两个数组总的长度
int tlen = alen+blen;
//如果是奇数
if (tlen % 2 == 1){
return BinarySearch(num1,num2,tlen/2+1);
}else{
//如果是偶数
return (BinarySearch(num1,num2,tlen/2+1)+BinarySearch(num1,num2,tlen/2+1))/2.0;
}
}
/**
* 二分查找法
* @param a
* @param b
* @param k 需要查找的第k个元素
* @return
*/
public double BinarySearch(int []a, int []b, int k){
int al = a.length, bl = b.length;
int ai =0, bi = 0;
int nai = 0, nbi = 0;
//只要还能继续二分查找
while (true){
//三种特殊情况
//a数组索引等于a数组的长度
if (ai == al){
return b[bi+k-1];
}
//b数组索引等于b数组的长度
if (bi == bl){
return a[ai+k-1];
}
//第k个字符是不是第一个
if (k == 1){
return Math.min(a[ai],b[bi]);
}
//常规操作,获取第k/2个元素的索引或者如果超出界限获取该数组最后一个元素
nai = Math.min(ai+k/2, al)-1;
nbi = Math.min(bi+k/2, bl)-1;
//两种常规情况
//该索引a数组大于b数组
if (a[nai] >= b[nbi]){
k-= nbi-bi+1;
bi = nbi+1;
}else{
//b数组大于a数组
k-=nai-ai+1;
ai = nai+1;
}
}
}
}