问题描述:
如果题目没有时间复杂度的要求的话,让人很容易就想到合并排序,遍历两个数组,然后比较两元素的大小,在第三个数组C上做插入排序,最后直接取数组C的中位数即可。时间复杂度为O(n),空间复杂度为O(m+n)。
题目要求的时间复杂度是O(log(m+n)),关于log,嗯,与分治的思想有关。首先,想到类似折半查找的思路去解决,但是自己折腾了好久,还是没写出来。不得已,只能看看大神是怎么解的。浏览大神们的博客,自己也理解了这其中的问题。
思路:
找出两个有序数组的中位数问题可以看成找出两个数组的第K位数问题。题目中两个数组时有序的,我们可以在数组A中遍历前k/2个元素(如果数组A的数组长度大于k/2的话),在数组B中遍历k-k/2个元素即可找到第k大的元素。如果A[p]<B[q],说明数组A的前p个数可以舍弃遍历,第k大的数在剩余数组里面,转而在剩余的数组里面寻找第k-p大的数。如果A[p]>B[q],说明数组B的前q个数可以舍弃遍历,第k大的数在剩余数组里面,转而在剩余的数组里面寻找第k-q大的数。将原问题分解成更小的问题,递归下去进行查找。findKth函数的形参包含了每个数组开始遍历的起始点,是为了方便更新下一步遍历的位置。
基于Java语言的代码如下:
public double findKth(int[] nums1,int[] nums2,int start1, int start2,int len1,int len2,int k)
{
//先对nums1长度进行判断
if(len1>len2)
{
return findKth(nums2, nums1, start2, start1, len1, len2, k);
}
//判断数组nums1为空的情况,直接返回nums2数组的第k个值
if(len1==0)
{
return nums2[start2+k-1];
}
if(k==1)
{
return Math.min(nums1[start1],nums2[start2]);
}
//nums1数组遍历下标
int p = Math.min(k/2,len1);
//nums2数组遍历下标
int q = k-p;
//针对<,>,=三种情况进行判断
if(nums1[start1+p-1]<nums2[start2+q-1])
{
return findKth(nums1, nums2, start1+p, start2, len1-p, len2, k-p);//更新数组起始位置和k的大小
}
else if(nums1[start1+p-1]>nums2[start2+q-1])
{
return findKth(nums1, nums2, start1, start2+q, len1, len2-q, k-q);
}
else
{
return nums1[start1+p-1];//正好找到第k个数
}
}
public double findMedianSortedArrays(int[] A, int[] B)
{
int Alen = A.length;
int Blen = B.length;
int total = Alen + Blen;
if((total&1) >0)//这里用按位与,速度是比%2==0的判断快的
return findKth(A, B, 0, 0, Alen, Blen, total / 2 + 1);
else
{
return (findKth(A, B, 0, 0, Alen, Blen, total / 2)
+ findKth(A, B, 0, 0, Alen, Blen, total / 2 + 1)) / 2;
}
}
算法的时间复杂度为O(log(m+n)),空间复杂度为O(1)。
在看完解释以后,由于还没理解清楚,在写findKth函数上犯了错误。导致算法在空间复杂度上出了问题,代码贴出来,以当经验。
public double findKth(int[] a, int m, int[] b, int n, int k)
{
if(m > n)
{
return findKth( b, n, a, m, k);
}
if(m == 0)
{
return b[k-1];
}
if (k == 1)
{
return Math.min(a[0], b[0])
};
//分治的思想
int pa = Math.min(k/2, m), pb = k - pa;//把数组a,b虚拟看成一个数组
if(a[pa-1] < b[pb-1])//如果数组a的第pa个的值小于数组b的第pb个值
{//舍弃以a[pa-1]为支点的左半部分
int[] str = new int[a.length-pa+1];
for(int i=pa;i<a.length;i++)//其实这里不必要的,既增加了代码量,又增加了复杂度
{
str[i-pa] = a[i];
}
//更新需要遍历数组的当前位置
return findKth(str, m - pa, b, n, k - pa);
}
else if(a[pa-1] > b[pb-1])
{
int[]str2=new int[b.length-pb+1];
for(int i=pb;i<b.length;i++)
{
str2[i-pb]=b[i];
}
return findKth(a, m, str2, n - pb, k - pb);
}
else
{
return a[pa-1];
}
}
学习参考链接: