第四题
题目要求
给定两个大小为 m 和 n 的有序数组 nums1
和 nums2
。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1
和 nums2
不会同时为空。
(There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
You may assume nums1 and nums2 cannot be both empty.)
示例
示例一:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
示例二:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
我的思路
类比归并排序的思路,首先计算数组长度,然后得到计算中位数的法则;之后使用双指针分别遍历两个数组,记录移动指针的次数,当达到中位数要求时,计算出相应的值。总的来说中规中矩;
这里的遍历过程需要分以下几种情况:
- 两个指针都还在数组之内,就已经找到中位数;
- 一个数组已经遍历结束,此时需要在另一个数组当中寻找中位数;
//71 ms
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int totalLength=nums1.length+nums2.length;//计算总的数目
boolean isEven=totalLength%2==0;
int firstPointer=0,secondPointer=0,medianPosition,step=-1;
int total;
if(isEven){//计算中位数的第一个数的“下标”
medianPosition=totalLength/2-1;//偶数个数字
}else{
medianPosition=totalLength/2;//奇数个数字
}
while(firstPointer< nums1.length&&secondPointer<nums2.length){//尚无数组遍历结束
if(nums1[firstPointer]<=nums2[secondPointer]){
total=nums1[firstPointer];
firstPointer++;
}else{
total=nums2[secondPointer];
secondPointer++;
}
step++;
if(step==medianPosition){//已经找到中位数的第一个数
if(isEven){
if(firstPointer<nums1.length&&secondPointer<nums2.length){//尚无数组遍历结束,第二个数需要比较得出
if(nums1[firstPointer]<=nums2[secondPointer]){
total+=nums1[firstPointer];
}else{
total+=nums2[secondPointer];
}
}else{//已经有数组遍历结束啦
if(firstPointer>=nums1.length){//第一个数组遍历结束
total+=nums2[secondPointer];
}else {
total += nums1[firstPointer];//第二个数组遍历结束
}
}
return total/2.0;
}else{
return total;
}
}
}//尚未找到中位数的第一个数,接下来要在一个数组里寻找;
if(firstPointer==nums1.length){
return finishFind(step,medianPosition,secondPointer,isEven,nums2);
}else{
return finishFind(step,medianPosition,firstPointer,isEven,nums1);
}
}
/**
* step:当前已经检查过的数字
* medianPosition:中位数所在的下标
* startPointer:开始寻找的下标
* isEven:是否是偶数
* nums: 中位数所在的数组
*/
public double finishFind(int step,int medianPosition,int startPointer,boolean isEven,int[] nums){
int total;
while(true){
total=nums[startPointer];
startPointer++;
step++;
if(step==medianPosition){
if(isEven){
total+=nums[startPointer];
return total/2.0;
}else{
return total+0.0;
}
}
}
}
优秀解法
//42ms
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
if (m < n) return findMedianSortedArrays(nums2, nums1);
if (n == 0) return (nums1[(m - 1) / 2] + nums1[m / 2]) / 2.0;
int left = 0, right = 2 * n;
while (left <= right) {
int mid2 = (left + right) / 2;
int mid1 = m + n - mid2;
double L1 = mid1 == 0 ? Double.MIN_VALUE : nums1[(mid1 - 1) / 2];
double L2 = mid2 == 0 ? Double.MIN_VALUE : nums2[(mid2 - 1) / 2];
double R1 = mid1 == m * 2 ? Double.MAX_VALUE : nums1[mid1 / 2];
double R2 = mid2 == n * 2 ? Double.MAX_VALUE : nums2[mid2 / 2];
if (L1 > R2) left = mid2 + 1;
else if (L2 > R1) right = mid2 - 1;
else return (Math.max(L1, L2) + Math.min(R1, R2)) / 2;
}
}
//33ms
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int nums2length = nums2.length;
int[] r = new int[nums1.length + nums2length];
int index = 0;
int j = 0;
for (int num : nums1) {
while (j < nums2length && num > nums2[j]) {
r[index] = nums2[j];
j++;
index++;
}
r[index] = num;
index++;
}
for (; j < nums2length; j++) {
r[index] = nums2[j];
index++;
}
int mi = r.length / 2;
return r.length % 2 == 1 ? r[mi] : (r[mi - 1] + r[mi]) / 2.0;
}
差别在哪里
第一种方法,是真没看懂;还需要斟酌斟酌,思考思考;
第二种方法的原理则是归并排序,归并的方式是先将数组A的所有元素先放入结果数组中,然后将数组B的剩余元素放入结果中;
我的方法则显得多此一举和笨拙;
多此一举表现在缓存了用来中位数的第一个数的值,引入该变量的目的就是处理总量为偶数的情况;但是,记录该变量实际上是一次数组访问,要达到同样的效果实际上只需要记录当前遇到的最大值是来自nums1还是nums2,这样的话,就可以使用boolean来记录,并且避免访问数组元素;
笨拙表现在finishFind函数里,我选择了遍历数组而不是通过变量计算出来:还需要移动的次数为medianPosition减去step;起点是startPointer,所以中位数的第一个值的下标为startPointer+medianPosition-step;