Description:
已知两个已经排好序(非减序)的序列X和Y,其中X的长度为m,Y长度为n,现在请你用分治算法,找出X和Y的第k小的数,算法时间复杂度为O(max{logm, logn})
分析:
既然说明了分治,那肯定是划分为子问题。又已经排好序,类比于二分查找,每次丢弃一半,我们可以将X的一半元素与Y的一半元素合并在一起,
再判断丢弃哪部分。取X[xBeg...xMid], Y[yBeg...yMid],合在一起的长度为halfLen
若 X[xMid] < Y[yMid],X[xBeg...xMid]一定小于Y[yMid]。若 k < halfLen,说明Y[yMid...yEnd]必定不可能出现第k小,丢弃。若 k >= halfLen,
说明X[xBeg...xMid]不可能出现第k小,因为X[xMid] < Y[yMid],极端情况下也只能是k-1小。所以丢弃X的左半段,此时k值发生改变,不再是找第k小,
而是 k-(xMid-xBeg+1)小。
同理X[xMid] >= Y[yMid]
递归边界的确定:
一半一半地丢弃下去,总会导致X或Y某一段不存在。所以递归边界为:
if(xBeg > xEnd) return Y[yBeg-k+1];
if(yBeg > yEnd) return X[xBeg-k+1];
#include <iostream> using namespace std; int find(int *x, int *y, int xbeg, int xend, int ybeg, int yend, int k){ if(xbeg > xend){ return y[ybeg + k - 1]; } if(ybeg > yend){ return x[xbeg + k -1]; } int xmid = (xbeg + xend)/2; int ymid = (ybeg + yend)/2; int halfLen = xmid - xbeg + ymid - ybeg + 2; if(x[xmid] < y[ymid]){//在合并的数组中,原X[xbeg...xmid]所有元素一定在Y[ymid]的左侧 if(k < halfLen){// y的右半段不需要寻找了 return find(x, y, xbeg, xend, ybeg, ymid-1, k); }else{// x的左半段不需要寻找了,因为X[xmid]<Y[ymid],所以极端情况x[xmid]也只能是k-1小的元素,所以取xmid+1到xend return find(x, y, xmid+1, xend, ybeg, yend, k-(xmid-xbeg+1));//注意k值的改变 } }else{//在合并的数组中,原Y[yBeg...yMid]的所有元素一定在X[xMid]的左侧 if(k < halfLen){ return find(x, y, xbeg, xmid-1, ybeg, yend, k); }else{ return find(x, y, xbeg, xend, ymid+1, yend, k-(ymid-ybeg+1)); } } } int main()//已知两个已经排好序(非减序)的序列X和Y,其中X的长度为m,Y长度为n, //现在请你用分治算法,找出X和Y的第k小的数,算法时间复杂度为O(max{logm, logn}) //吐槽: 直接把两个数组合并一下,再用数组自带的sort排个序(快速排序O(nlogn)),再找第K小,岂不美滋滋,O(m+n) { int m, n, k; cin >> m >> n >> k; int x[m]; int y[n]; for(int i=0; i<m; i++){ cin >> x[i]; } for(int j=0; j<n; j++){ cin >> y[j]; } cout << find(x, y, 0, m-1, 0, n-1, k) << endl; }