从俩个有序数组中找出第K小的数。要求时间复杂度O(logmin(m,n))

时间:2023-01-05 22:02:25

思路

  该题目要求时间复杂度为O(log(min{m,n})) 所以不能直接遍历任意一个数组这样时间复杂度就不符合了。也不能对任意一数组进行二分查找,因为要求是俩个数组元素合并后的第K小的数,所以直接遍历用二分遍历任意一个数组也是行不通的。
故我们可以以区间的方式解决这个问题。我们先假设第一个数组个数小于等于第二个数组个数。如果不是则交换俩个数组即可。这样的话,先定义俩个区间,第一个区间range大小为min(2/k,len1),这个区间最大为k/2,最小为1.第二个区间就是K-range大小。

我们可以通过查看第一个区间的最后一个元素和第二个区间的最后一个元素的值大小来缩短区间最终找到第k个元素这分三种情况:
1.如果第一个区间最后一个元素的值等于第二个区间最后一个元素的值。那么区间的最后一个元素就是第k个元素。因为这俩个区间个数加起来等于k,并且这俩个区间值又是相等的,那么说明我们筛选出来了前K小的元素区间,所以最后一个元素是第k小的元素。
2.如果第一个区间最后一个元素值比第二个区间最后一个元素值小。那么说明第一个区间所有元素都是前k小区间元素中的元素。所以我们可以继续遍历,此时不在查找第k小个数了,更新到了查找第k-range1小的数了。那么我们可以缩短第一个数组,第二个数组不变继续遍历。
3.如果第二个区间最后一个元素值比第义个区间最后一个元素值小。那么说明第二个区间所有元素都是前k小区间元素中的元素。所以我们可以继续遍历,此时不在查找第k小个数了,更新到了查找第k-range2小的数了。那么我们可以缩短第二个数组,第一个数组不变继续遍历。

那么当有一个数组个数被筛选的缩短至0时,答案就出来了,此时第k个元素必定在第二个数组直接就可以找到了。
如果k缩小到1了,答案也出来了,那就是俩个缩短后的数组中的首元素的较小值为第k个元素。
所以这就是以上思路。

代码

int FindKthNum(int * arr1, int len1, int*arr2, int len2, int kth)
{
assert(arr1 != NULL&&arr2 != NULL);
assert(len1 + len2 > kth);
if (kth == 1) return min(arr1[0], arr2[0]);
if (len1 > len2) return FindKthNum(arr2, len2, arr1, len1, kth);
if (len1 == 0) return arr2[kth];//为了后续递归时,如果第一个区间筛选完了,那么结果肯定就在第二个区间了
int range = std::min(len1, kth / 2);
int range2 = kth - range;
if (arr1[range - 1] == arr2[range2 - 1])
return arr1[range - 1];
if (arr1[range - 1] < arr2[range2 - 1])
return FindKthNum(arr1 + range, len1 - range, arr2, len2, kth - range);
if (arr2[range2 - 1] < arr1[range - 1])
return FindKthNum(arr1, len1, arr2+range2, len2 - range2, kth - range2);
}