求已知N个数中第k小的数

http://eager2007.blog.hexun.com/26216090_d.html

本来今天想写的是“面试记”,八一八HR姐姐和面试官叔叔。但聊到面试难免要聊面试题,而算法题自是其重中之重。为了日后能专心地八面试官,今天就先说说这个算法。。。
本篇涉及专业知识,外行止步,发生危险概不负责。

算法与数据结构,属于IT技能中较“高雅”的一类,所谓阳春白雪、曲高和寡,往往只有大公司才对此有兴趣。而急功近利的小公司面试官只会问“会不会用Struts”之类……
其 中,数据结构比较基础些。一般开发类职位都会考考Linked List或者Hash table之类的,相信上过这门课的人都能写出来。要求更高的面试官会真正地考算法,(而不仅仅是数据结构)。算法有两种考法,一是考课本知识,主要集中 在排序、查找和二叉树相关三类算法。考查的是基础知识掌握,以及编程基本功。
另一类就是考书上没有的算法,这往往出现在对创造性较高的岗位中。想要令面试官满意的话,完全寄希望于现场发挥是很难的,最好平时有算法方面的积累。建议大家平时多逛逛百合的Algorithm版什么的……另外百度历年的笔试题也是增加积累的好素材。
最后不得不提一个出场率最高的算法,我曾经在四场面试中用上它:

问题:[Random Select]求已知N个数中第k小的数。(k<N)

先排序再取数的做法需要O(n*Logn)的复杂度,事实上此问题O(n)可解。具体做法是:按照类似快速排序算 法中的“分类”步骤,任选一个参考数,把整个数组分成左右两部分。左边部分小于参考数,右边部分大于参考数。然后根据左右部分各自包含元素的个数,计算出 要求的元素在哪个数组中。(要求的元素或者是左数组中的第k小数,或者是右数组中第[k-l]小的数。l为左数组的长度)然后递归之。
贴个伪代码:

int select(int[] arr, int lw, int hi, int k)
// lw:下标下界, hi:下标上届。
{
    if(lw==hi)
          return arr[lw];
   int q = partition(lw,hi);//调用quicksort里面的partition, q指向参考数
   int l = q-lw+1;  // l为左数组长度
   if(k==l)
           return arr[q];
   if(k<l)
           return select(arr,lw,q-1,k);
   if(k>l)
           return select(arr,q+1,hi,k-l);
}

没看懂的话也没关系,百度一下“第k小数”或者“random select问题”会有更详细的解释。

如何向面试官描述这个算法:
如果手中有纸笔,画图可以描述地更清楚。如果是电话面抓住关键词“快排”“分类”“递归”即可。
进一步,面试官会问这个算法的复杂度。此算法额外空间复杂度为O(logn),可以进一步优化为O(1)——将递归改写为非递归即可。时间复杂度为O(n)——虽然乍看上似乎是n*Logn。平均每个数组元素被访问两次。
时间复杂度的证明是另一个关键,如果证不出来赶快百度一下啦。

下面解释为什么这个算法在面试中的应用

首先,此问题可以推广:求数组中最小的k个数(而不是第k小的数)。答案很简单:求出第k小数之后,其左边的(k-1)个数都比它小。所以……


其次,它可以退化求中位数问题:如何在O(n)时间内求数组的中位数。
我曾在某次面试中被提了这个问题,随后面试官又推广了此问题。他问道:

如果有一个数组,对其取中位数,然后移除此元素。重复此操作k次,求这k个被移除的数。如何设计算法?

(注,当数组长度为偶数时,中位数有两种不同的定义。此处定义第[n/2]个数为中位数,而不是中间两数之平均。)
也许不可思议,此问题仍然有O(n)的解法:
可以证明:如果将数组排序,那么问题等价于求数组最中间的一段k个数。当然“最中间一段”这样的说法有些含糊,但大致上就是求下标为[n/2-k/2, n/2+k/2]这个区间上的一段数,但是在区间两端要根据n和k的奇偶性作+1或-1的调整。因此,原问题转化为:

求某个数组中第a小到第b小之间的k个数。(b>=a,k==b-a+1)

剩下的问题就很简单了:先求原数组中最小的b个数,然后在此结果中求最大的(b-a+1)个数。


最后一个推广,此算法还可以用在著名的“主元素”问题上。主元素的定义是:

如果一个数组中,有一个数出现次数达到数组长度的一半以上,则称其为数组的 主元素

主元素问题就是:

给定一个数组,判断其是否有主元素,并求这个元素。

使用Random select算法,可以在O(n)时间内解决此问题:1 求中位数。2 检验中位数是否为主元素。
不过这样需要平均扫描每个元素3次。事实上此问题还有更巧妙的解法,可以只扫描每个元素2次。欲知解法如何,去Google上百度一下……

你可能感兴趣的:(求已知N个数中第k小的数)