题目
给定两个大小为 m 和 n 的有序数组 nums1
和 nums2
。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1
和 nums2
不会同时为空。
解析
确定中位数的原理
1.由于时间复杂度要求肯定要用二分法
2.中位数代表着比其小的数的数量,等于比其大的数的数量,那么可以确定给两个数组下标分别使两下标左边的数的数量等于右边,则两下标i,j的和i+j=(m+n+1)/2
3.B[j−1]<=A[i] 且A[j−1]<=B[i]时中值就在这四个数内
寻找中位数的方法 ——二分查找
1.把i设为m/2(j=(m+n+1)/2-i,)开始比较,如果B[j−1]>=A[i],那么A[i]太小,要用类似折半查找的方式,在A数组更大的一半中继续找(选中间的比较),A[j−1]>=B[i]则A[i]太大在A数组更小的一半中继续找
2.当B[j−1]<=A[i] 且A[j−1]<=B[i]成立时就要考虑各种特殊情况。比如m+n是奇数还是偶数,i,j有没有到了数列两边极限
我是使用迭代的方法,是在答案基础上改的,但其实更慢,不过时间复杂度也是为 O(log(m + n))
package leetcode4.寻找两个有序数组的中位数;
import java.util.Arrays;
public class middle
{
public static void main(String[] args)
{
int[] a=youXuShuZu(0,1000000000,30000000);
int[] b=youXuShuZu(800000000,1800000000,40000000);
//以下是用来比较两方法的速度,结果是我的慢一点
{
long t1,t2;//引入时间
t1 = System.currentTimeMillis();//计时器
double mid1=0;
for(int i=0;i<100000;i++)
{
mid1=findMedianSortedArrays1(a, b) ;
}
t2 = System.currentTimeMillis();
System.out.println("二分迭代的方法"+(t2-t1)+"ms");//输出用时
System.out.println("中位数"+mid1);
}
{
long t1,t2;//引入时间
t1 = System.currentTimeMillis();//计时器
double mid2=0;
for(int j=0;j<100000;j++)
{
mid2=findMedianSortedArrays2(a, b) ;
}
t2 = System.currentTimeMillis();
System.out.println("leetcode的方法"+(t2-t1)+"ms");//输出用时
System.out.println("中位数"+mid2);
}
}
//生成有序数组,确定开头,结尾,和数组数字量
public static int[] youXuShuZu(int start,int end,int number)
{
int[] a=new int[number];
for(int i=0;i<a.length;i++)
{
a[i]=(int)(Math.random()*(end-start)+start);
}
Arrays.sort(a);
//System.out.println("有序数组"+Arrays.toString(a));
return a;
}
//我的是使用迭代的方法
public static double findMedianSortedArrays1(int[] A, int[] B)
{
int m = A.length;
int n = B.length;
double mid;
if (m > n) { // to ensure m<=n
int[] temp = A; A = B; B = temp;
int tmp = m; m = n; n = tmp;
}
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
mid=dieDai(A,B,iMin,iMax,halfLen);
return mid;
}
//leetcode那个标准答案只是第一步二分了,后面没有实际上时间复杂度不是O(log(m+n)),要像下面这种迭代了的才是
public static double dieDai(int[] A,int[] B,int iMin,int iMax,int halfLen)
{
int i = (iMin + iMax) / 2;//两者之和为奇数,则A[i]为A数组中数,两者之和为偶数,则A[i-1]和A[i]共同为中数
int j = halfLen - i;
//跳出这个循环的第一种情况B[j-1] <= A[i]
if (i < iMax && B[j-1] > A[i])
{
// i is too small
iMin=i+1;//为什么是iMax=i呢,因为A[i-1] > B[j]的话,最后的中位数肯定不是A[i],所以类比于从0开始,现在要从i+1开始
return dieDai(A,B,iMin,iMax,halfLen);
}
//跳出这个循环的第一种情况A[i]>=B[j-1],A[i-1] <=B[j],如果是这种情况i起码是以iMin+1跳出,如果第二种情况则由于i=iMin跳出,即A[i]>B[j]是符合的
else if (i > iMin && A[i-1] > B[j])
{
// i is too big
iMax=i; //为什么是iMax=i呢,因为A[i-1] > B[j]的话,最后的中位数肯定不是A[i],类比以前以a.length+1(即m),现在要从i-1+1开始
return dieDai(A,B,iMin,iMax,halfLen);
}
else
{ // i is perfect
//这里分析的时候一定要用答案解析中的分两块的思路,即答案肯定在A[i],A[i-1],B[j],B[j-1],之内,不要来回分析数组长度先奇后偶,或反过来有什么区别,这样来回分析情况太多
//是不是中数,看比这个数小的数有多少就可以了
int maxLeft = 0;
if (i == 0) { maxLeft = B[j-1]; }//就是i已经是a数组最左,且i==0即i==iMin跳出说明A[0]>B[j]也>B[j-1],那么,那么b数组取j-1,因为m,n都是数组长度,是数组最右端下标加1,所以如果一奇一偶的话j-1是中值,如果是两奇或两偶j-1则是左中值,因为A[0]>B[j-1]
else if (j == 0) { maxLeft = A[i-1]; }//这种情况是n=m,则这种情况下i=iMax=n,同上道理i-1是中值或左中值
else { maxLeft = Math.max(A[i-1], B[j-1]); }//m+n为偶数时,必为左中数,m+n为单数时,加入A[i-1]>B[j-1],那么比A[i-1]小的数有,(i-1)+(j-1+1)正好是m+n-1的一半,也就是说A[i-1]是中位数,对于B[j-1]>A[i-1]的情况同理
if ( (A.length + B.length) % 2 == 1 ) { return maxLeft; }//根据上诉分析如果m+n为单数,那么左中值就是中值
int minRight = 0;
if (i == A.length) { minRight = B[j]; }//同上分析
else if (j == B.length) { minRight = A[i]; }
else { minRight = Math.min(B[j], A[i]); }
return (maxLeft + minRight) / 2.0;
}
}
//leetcode的方法
public static double findMedianSortedArrays2(int[] A, int[] B)
{
int m = A.length;
int n = B.length;
if (m > n) { // to ensure m<=n
int[] temp = A; A = B; B = temp;
int tmp = m; m = n; n = tmp;
}
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
int j = halfLen - i;
if (i < iMax && B[j-1] > A[i]){
iMin = i + 1; // i is too small
}
else if (i > iMin && A[i-1] > B[j]) {
iMax = i - 1; // i is too big
}
else { // i is perfect
int maxLeft = 0;
if (i == 0) { maxLeft = B[j-1]; }
else if (j == 0) { maxLeft = A[i-1]; }
else { maxLeft = Math.max(A[i-1], B[j-1]); }
if ( (m + n) % 2 == 1 ) { return maxLeft; }
int minRight = 0;
if (i == m) { minRight = B[j]; }
else if (j == n) { minRight = A[i]; }
else { minRight = Math.min(B[j], A[i]); }
return (maxLeft + minRight) / 2.0;
}
}
return 0.0;
}
}