寻找两个有序数组中的第k个数
假设有两个数组A与B
A为[1,2,5,6.7]
B为[1,2,3,4,5]
现在需要寻找A、B合并之后的数组中的第4个数
方法一
比较偷懒的方法
直接使用C++中的merge函数将A与B合并为一个有序数组再按照顺序进行查找,时间复杂度为O(n+m)
方法二
使用二分法进行查找
我们首先需要明确目标,是在有序数组(尽管查找之前需要合并)中寻找第k个数,既然是有序数组,我们就想要利用有序这个特性,也就会自然想到二分查找算法。
但是现在的障碍是我们无法直接一下直接使用二分查找,因为是两个数组。
我们可以先画出A和B的图,如下所示
**我们的目标是找到第k个数,但是我们可以先找到这个k个数里面的k/2个数 **
(注意:此处数组的下标为k/2-1是因为 从0到k/2-1有k/2个数)
假设A[k/2-1]<B[k/2-1],我们可以得出A[0]—A[k/2-1]的这些数字一定都在前k个数字当中。因为如果说A[k/2-1]恰好等于B[k/2-1],第k个数字肯定就是A[k/2-1]或者B[k/2-1],但是现在A[k/2-1]<B[k/2-1],因此前k个数字当中一定有k/2个数是A[0]-A[k/2-1]的这些数,我们此时不知道前k/2个数究竟是不是就是A[0]-A[k/2-1],也无需考虑这些,只要知道前k个数字中我们已经找到了一半的数字,剩下的工作就是再找剩下的一半数字。
那么我们只需要将寻找的范围缩小到剩下的数字即可,但是我每次查找的时候只查找剩下个数的一半
对于A的再次寻找范围为:A[k/2]—A[k/2+(k/2/2)-1] (此处的k/2+(k/2/2)-1是因为每次我只查找剩下需要查找个数的一半,同时k/2+(k/2/2)-1到k/2有k/2/2个数字)
对于B的再次寻找范围为:B[0]—B[k/2/2-1]
假设A[k/2+(k/2/2)-1]<B[k/2/2-1],那么和上面一样,我又可以再次找到属于前k个数当中的k/2/2个数字。
那么我只剩下了k/2/2个数字需要查找了。
如此递归下去直到需要查找的数字只剩下一个的时候,那么这个数字必定就是我们需要找到的第k个数字
为什么呢?因为我们之前找到的数字都是小于k的。
为什么都是小于k的呢?因为我们从来没有将比较大的那个数字划分到前k个数字当中。比如说第一次寻找k/2个数字的时候,我们并没有将较大的B[k/2-1]加入到已经找到的数字当中,而是将A[0]—A[k/2-1]这个k/2个数字加入到已经找到数字当中。第二次寻找k/2/2个数字的时候同样没有将较大的B[k/2/2-1]加入到已经找到的数字当中,而是将A[k/2]-A[k/2+k/2/2-1]加入到已经找到的数字当中。
从而当最后只剩下一个数字需要寻找的时候,那个数字必然就是第k个数字。
代码如下
int findKthNumber(const vector<int> &v1,int v1Start,const vector<int> &v2,int v2Start,int k){
//当v1元素个数比较少的时候,一直+会导致其start超过其size,那我只需要返回我此轮需要找到的个数即可,比如说我此轮还差两个就满k个数字了,那我就返回此轮v2的start之后的第二个数字就行了
if(v1Start>=v1.size()){
return v2[v2Start+k-1];
}
//同上
if(v2Start>=v2.size()){
return v1[v1Start+k-1];
}
//跳出递归的条件:只剩下一个数字需要寻找
if(k==1){
return v1[v1Start]<=v2[v2Start]?v1[v1Start]:v2[v2Start];
}
//此处的INT_MAX主要防止数组元素个数比较少但此时该数字还未遍历完全,在寻找的时候在+k/2-1之后超过本身的size,如果超过本身的size,那我直接从另外一个数组寻找此轮需要的个数就行
int v1Key = v1Start+k/2-1<v1.size()?
v1[v1Start+k/2-1]:INT_MAX;
int v2Key = v2Start+k/2-1<v2.size()?
v2[v2Start+k/2-1]:INT_MAX;
//关键:寻找的个数一直在变少!!!
if(v1Key<v2Key){
return findKthNumber(v1,v1Start+k/2,v2,v2Start,k-k/2);
}
else
{
return findKthNumber(v1,v1Start,v2,v2Start+k/2,k-k/2);
}
}