描述:
There are two sorted arrays A and B 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)).
分析:
其实这题更通用的形式是,给定两个已经排好序的数组,找到两者所有元素中第k大的元素。
O(m+n)的解法比较直观,直接merge两个数组,然后取第k个元素即可。
不过我们仅仅需要第k大的元素,是不需要“排序”这么复杂的操作的。可以用一个计数器,记录当前已经找到第m大的元素,同时我们使用两个指针pA和pB,分别指向A和B数组的第一个元素,使用类似于merge sort的原理,如果数组A当前元素小,那么pA++,同时m++;如果数组B当前元素小,那么pB++,m++。最终当m等于k的时候,就得到了我们的答案,O(k)时间,O(1)空间。如果当k接近m+n的时候,这个方法还是O(m+n)的。
实现代码:
#include<stdio.h> static int g_InvlidInput = 0; int find_kth(int A[],int m, int B[], int n,int k) { if((m+n)<k) { g_InvlidInput = 1; return -1; } int kth = 0,kthindex = 0,Aindex = 0,Bindex = 0; int *pA = A; int *pB = B; while((kthindex < k) && (Aindex < m) && (Bindex < n)) { if(pA[Aindex] <pB[Bindex]) { kth = pA[Aindex]; Aindex++; }else { kth = pB[Bindex]; Bindex++; } kthindex++; } if(kthindex == k) return kth; else { if(Aindex == m) return pB[Bindex+k-kthindex]; if(Bindex == n) return pA[Aindex+k-kthindex]; } } //test int main() { int A[] ={1,4,5,7,9,22,44,78}; int B[] ={2,4,7,11,12,23,45}; int result = find_kth(A,8,B,7,8); if(g_InvlidInput) printf("Invaild Input!"); else printf("%d ",result); return 0; }
除了这个方案,还有没有更好的方案呢?有。我们可以考虑从k入手。如果我们每次都删除一半呢》由于A和B都是有序的,我们应该充分利用这里面的信息,类似于二分查找,也是充分利用了“有序”。
假设A和B的元素个数都大于k/2,我们将A的第k/2个元素(即A[k/2-1])和B的第k/2个元素(即B[k/2-1])进行比较,有以下三种情况(为了简化这里先假设k为偶数,所得到的结论对于k为奇数也是成立的):
- A[k/2-1] == B[k/2-1]
- A[k/2-1] > B[k/2-1]
- A[k/2-1] < B[k/2-1]
如果A[k/2-1] < B[k/2-1] ,意味着A[0]到A[k/2-1]的元素肯定在AUB的top k元素的范围内,也就是说,A[K/2-1]不可能大于AUB的第k大元素。
因此,我们可以放心的删除A数组的这k/2个元素。同理,当A[k/2-1] > B[k/2-1]时,可以删除B数组的k/2个元素。
当A[k/2-1] == B[k/2-1]时,说明找到了第k大的元素,直接返回A[k/2-1]或B[k/2-1]即可。
因此,我们可与写一个递归函数,那么函数该什么时候终止呢?
- 当A或B是空时,直接返回B[k-1]或A[k-1];
- 当k=1时,返回min(A[0],B[0]);
- 当A[k/2-1] == B[k/2-1]时,返回A[k/2-1]或B[k/2-1]
实现代码:
#include<stdio.h> int min(int a, int b) { if(a>b) return b; else return a; } int find_kth(int A[],int m, int B[], int n,int k) { //保证m比n小 if(m >n) return find_kth(B,n,A,m,k); if(m == 0) return B[k-1]; if(k == 1) return min(A[0],B[0]); //将k拆分成两个部分 int pa = min(k/2,m),pb = k -pa; if(A[pa-1] < B[pb-1]) return find_kth(A+pa,m-pa,B,n,k-pa); else if( A[pa-1] >B[pb-1]) return find_kth(A,m,B+pb,n-pb,k-pb); else return A[pa-1]; } //test int main() { int A[] ={1,4,5,7,9,22,44,78}; int B[] ={2,4,7,11,12,23,45}; int result = find_kth(A,8,B,7,10); printf("%d ",result); return 0; }