题目为:
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)).
大意就是求出两个已排序数组的中位数. 如果总共有偶数个元素, 返回两个中间数的和的一半. 所以返回类型是double
还要求时间复杂度为O(log (m+n)).
初看这题,马上想到的就是使用 merge sort,将两个数组合并起来后直接返回中间数, 这样非常省力.但时间复杂度平均下俩就是O(m+n)了, 而不是要求的O(log (m+n)).
如下所示:
double findMedianSortedArrays(int A[], int m, int B[], int n) {
int i = 0, j = 0, k = 0;
int *C = new int[m+n];
while (i < m && j < n)
{
if (A[i] < B[j])
C[k++] = A[i++];
else
C[k++] = B[j++];
}
while (i < m)
C[k++] = A[i++];
while (j < n)
C[k++] = B[j++];
int mid = (m + n) / 2;
if ((m + n) % 2 == 0)
return (double)(C[mid] + C[mid - 1]) / 2;
else
return C[mid];
}
既然无法达到要求, 那么只能换一种思路. 根据题目给出的时间复杂度, 我们一般会想到的排序方法有merge sort, quick sort等
对于查找的算法, 比如二分查找, 或是利用二叉树的变形来查找, 哈希查找显然更快, 但明显不适合这里.
所以, 最终选择了二分查找.
下面是参考了别人的代码:
#include <stdio.h>
//这个二分查找, 找到目标value就会返回其下标; 如果没有找到, 会返回它应该插入的位置
int BinarySearch(int *nums, int start, int end, int value){
int mid;
while(start <= end){
mid = start + (end - start) / 2;
if(nums[mid] >= value)
end = mid - 1;
else
start = mid + 1;
}
return start;
}
//以下部分参数为二分查找所需
double recusiveFindMedian(int *A, int m, int *B, int n, int A_Start, int A_End, int B_Start, int B_End){
//A_Mid表示A数组中间数的下标, 也表示在A数组中, 该数之前有多少个数字
int A_Mid = A_Start + (A_End - A_Start) / 2;
//找到A数组的中间数在B中的位置. 也表示在B数组中, 该数之前有多少个数字
int A_Mid_Pos_In_B = BinarySearch(B, B_Start, B_End, A[A_Mid]);
//最终就得到了合并后, 该数之前会有多少个数存在
int A_Mid_Final = A_Mid + A_Mid_Pos_In_B;
//如果这个数在最终合并的数组中仍然处于中间位置
//那么就继续考虑合并后的数组的元素个数是偶数还是奇数.
//如果合并后有奇数个数字, 直接返回这个数;
//如果合并后有偶数个数字, 那么返回这个数和这个数之前的数的和的一半
if(A_Mid_Final == (m + n)/2){
//比如数组A:[1, 3, 5]; 数组B:[2, 4]; 那么
// A_Mid = 1; A_Mid_Pos_In_B = 1; A_Mid_Final = 2 == (m + n) / 2; 直接返回就可以
//比如数组A:[3, 5]; 数组B:[1, 2, 4]; 那么
// A_Mid = 0; A_Mid_Pos_In_B = 2; A_Mid_Final = 2 == (m + n) / 2; 依旧直接返回
//比如数组A:[5, 9]; 数组B:[2, 4]; 那么
// A_Mid = 0; A_Mid_Pos_In_B = 2; A_Mid_Final = 2 == (m + n) / 2;
// 因为是偶数个元素, 所以最终返回 (A[0] +B [1])/2 = 4.5
if((m + n) & 1)
return A[A_Mid];
int prev;
//既然 A_Mid 等于偶数的一半, 比如(2+4)/2 = 3; 那么最终的返回值应该是合并后的(M[3]+M[2])/2才对
//因为 0,1,2,3,4,5 中间两个数是2和3
//但是如果出现 (1 + 3)/2 = 2的情况,比如
// [2, 3, 4]; [5]; 此时A_Mid_Pos_In_B = 0, 这时候只能返回 (A[1]+A[2])/2
//又或者这种情况:
// [3]; [2, 4 ,5]; 此时就要 (A[0] + B[1])/2
//又或者两数组都只有1个元素, 这样只要返回两数和的一半就行了
//其他普通情况就是从A数组或B数组中挑一个小于A_Mid又离的最近的就行了
if(A_Mid > 0 && A_Mid_Pos_In_B > 0)
prev = A[A_Mid-1] > B[A_Mid_Pos_In_B-1] ? A[A_Mid-1] : B[A_Mid_Pos_In_B-1];
else if(A_Mid > 0)
prev = A[A_Mid - 1];
else if(A_Mid_Pos_In_B > 0)
prev = B[A_Mid_Pos_In_B - 1];
else
prev = B[A_Mid_Pos_In_B];
return (A[A_Mid] + prev) / 2.0 ;
}
else if(A_Mid_Final < (m + n)/2){
//既然这个数在合并后的数组中的位置是在左半边的, 那么我们就要用比这个数大的数来继续寻找
//就是二分查找的概念, 现在往右边找
//在A数组中, A_Mid肯定不符合要求了, 所以加一往后找
//在B数组中, A_Mid_Pos_In_B可能与A_Mid的值相同, 也可能是A_Mid对应的值应该要插入的位置, 所以不能加一
A_Start = A_Mid + 1;
B_Start = A_Mid_Pos_In_B;
if((A_End - A_Start) > (B_End - B_Start))
return recusiveFindMedian(A, m, B, n, A_Start, A_End, B_Start, B_End);
return recusiveFindMedian(B, n, A, m, B_Start, B_End, A_Start, A_End);
}
else{
//这里能减1是有多个条件促成的
// A数组是较长的数组
// A数组中的中间数在合并后的数组的右边
A_End = A_Mid - 1;
//B数组能舍弃一些元素是因为, 两个数组是sorted的
//这里能减1也是因为A_Mid_Pos_In_B可能与A_Mid的值相同, 也可能是A_Mid对应的值应该要插入的位置
B_End = A_Mid_Pos_In_B - 1;
if((A_End - A_Start) > (B_End - B_Start))
return recusiveFindMedian(A, m, B, n, A_Start, A_End, B_Start, B_End);
return recusiveFindMedian(B, n, A, m, B_Start, B_End, A_Start, A_End);
}
}
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size){
if(nums1Size == 0 && nums2Size == 0)
return 0.0;
if(nums1Size == 0)
return (nums2Size%2==1 ? nums2[nums2Size/2] : (nums2[nums2Size/2-1] + nums2[nums2Size/2])/2.0);
if(nums2Size == 0)
return (nums1Size%2==1 ? nums1[nums1Size/2] : (nums1[nums1Size/2-1] + nums1[nums1Size/2])/2.0);
//下面我们保证recusiveFindMedian中A的数组长度一定是不小于B的
if(nums1Size > nums2Size)
return recusiveFindMedian(nums1, nums1Size, nums2, nums2Size, 0, nums1Size-1, 0, nums2Size-1);
return recusiveFindMedian(nums2, nums2Size, nums1, nums1Size, 0, nums2Size-1, 0, nums1Size-1);
}
int main()
{
int ar1[] = {1, 12, 15, 26, 38};
int ar2[] = {2, 13, 17, 30, 45, 50};
printf("Median is 17 = %f\n", findMedianSortedArrays(ar1, 5, ar2, 6));
return 0;
}
总体的思想是利用较长数组的中间数在合并后数组中的位置来进行的二分查找.
这又让人想起了快速排序中的Partition算法...