这是曾经一次面试时被问到的题。
当时很直观的是想到先用归并排序中的合并两个列表的方法把列表合并起来,一边合并一边记count,等count等于n时就返回当前处理的元素。
这是最原始的做法。面试官问还有没有优化版本。
当时一时半会儿没什么思路,总的思路是想想有没有什么办法能缩小查询范围。
面试官也多次提示。最后初步得出一个方案,大概是:
- 比较两个数列的位于中间的元素哪个大,如果某一个数列(数列A)中第n/2个元素比另一个数列(数列B)的要小,那说明这个数列的第1到n/2个元素肯定在合并后数列的前n个元素中。
- 然后再取出数列A中的位于第n/2+1到n的元素段的中间元素去和数列B中之前的中间位置的元素比较,如果数列A中的这个元素比数列B中的这个元素小,就又继续取数列A中后面的元素段中的中间元素(Binary Search),否则这次轮到用数列A中的这个元素跟数列B中第n/2到n的元素段的中间元素去比较。
- 重复前两步,直到最后把比较范围从元素段缩小到单个元素。
在这个算法里有很多细节要注意,比如在取中间元素时要注意数列长度是奇数还是偶数,两个数列不等长的问题以及在这种情况下以中间段元素选取的调整等。
基于这个算法, 一般情况下,选取位于合并后数列靠近两端(无论前后)的元素所花时间会比靠近中间的元素时间要小。
大家可以在这里下载源代码,并编译执行(基于.net 2.0)。
这里是算法演示动画程序,大家可以在这个程序里设置两个数列的长度(List A/B Length)和数据随机增长率(List A/B Gap Rate),然后设定要选择第几项(Fetch Index),然后就可以观看算法动画。
照例上传一个算法演示动画(数列A和B长度都为30,选取合并后第40个元素):