[京东面试问题] 求n=100w个数里面的前k=100个

来源:互联网 发布:淘宝十大情侣装店铺名 编辑:程序博客网 时间:2024/06/08 09:06

# 基本思路

  最复杂的方法莫过于全部排序, 复杂度nlgn. 结合求100w个数里面最大的一个数(即求max), 不需全排序, 只需开一个变量, 遍历一次记录最大值即可.那么很自然的想到, 求前k个就是开一个k大小的数组, 把历史最大/次大/次次大存起来. 也就是说, 在遍历n的时候, 对于新元素需要和k数组进行对比, 最差的方式是每次遍历k, 总体复杂度在n*k. 如果k数组是已排序的, 那么新元素可以更快的对比, 也就是二分查找, 找到新元素在数组k中的定位, 替换旧值.

何时替换呢? 因为是寻找最大的k个, 排序后, 新元素如果比k中最小值还小, 直接舍弃, 否则就二分的找到其应在的位置. 总体复杂度为nlgk. 

既然想到了二分法, 处于数据结构的考虑, 可以用搜索二叉树来代替二分的过程.

当然, 也很自然的联想到堆. 因为上述方法只用k的存储空间即可, 如果采用最大堆, 因为只知道最大值比如为5, 如果新元素为4, 并不能很好的放入左右子树, 使保持总体有序. 


# 最小堆

看网友的方法, 才恍然大悟. 为什么我要用最大堆呢? 因为被"求最大值"这个惯性思维给迷惑了. 其实应该维护一个最小堆:

先取出前100个数,维护一个100个数的最小堆,遍历一遍剩余的元素,在此过程中维护堆就可以了。具体步骤如下: 
step1:取前m个元素(例如m=100),建立一个小顶堆。保持一个小顶堆得性质的步骤,运行时间为O(lgm);建立一个小顶堆运行时间为m*O(lgm)=O(m lgm);       
step2:顺序读取后续元素,直到结束。每次读取一个元素,如果该元素比堆顶元素小,直接丢弃 
如果大于堆顶元素,则用该元素替换堆顶元素,然后保持最小堆性质。最坏情况是每次都需要替换掉堆顶的最小元素,因此需要维护堆的代价为(N-m)*O(lgm); 
最后这个堆中的元素就是前最大的10W个。时间复杂度为O(N lgm)。 


每次比较的对象是根节点, 也就是堆中的最小值, 实际上和上面所说的方法很相似, 区别在于, 用最小堆每次给出min值, 因为维护top k也是替换min的过程!!!! 但是, 这个最小堆并不是可以直接对外有序的, 其实也刚好对应了题目的要求, 只要top k, 没有要求顺序输出. 

区别: 

- 第一种方式先排序, 后维护这个顺序, 因为有序, 所以可以用二分的方式维护顺序. 结果可以直接输出有序的.

- 第二种方式先维护最小堆, 最后如果有需求顺序输出, 可后排序. 

共性:

- 都是在遍历n时, 和k数组的min做对比, 然后把旧min剔除, 放入新元素. 

思路如果仔细思考共性这条, 就很容易理解第二种方式了, 这个方式很优秀!

// csdn markdown 挂掉了 , 暂时随手一记, 回头修整


# 参考
原创粉丝点击