前几天去了趟北京找工作,自信满满,却碰了一鼻子灰回来。甚是不爽啊。很多面试题当时没做出来,但是回来查一下谷歌,或者自己思考一下,基本也就解决了。
1. 给定一个班里的成绩,给你一个分数,输出这个分数的排名。
继续追问:不需要是谁,重复分数算在内。比如4个100分,99分就是第5名。
我的答案:拿着这个分数去遍历数组,大于它的就拿出来,计算一下length即可。算法复杂度O(n)。
2. 给定一个班里M个人的成绩,输出每个分数的名次。
继续追问:分数都是整数。
我的答案:以空间换时间,按照顺序建立101个数组,遍历数组,把相应的人加入相应的数组,然后再输出,旁边附上一个自加值即可。时间O(n),空间2n 。
增加要求:有没有可能空间复杂度为n?
我没有想到...
面试官答案:为什么要记人呢,为什么要记分数呢?只需要记住几个就可以了。建立一个字典,甚至只是一个数组,遍历数组,相应的位置加1即可。
————————————
虽然没答出来,但是这是我最成功的一次面试了,我反应奇快。
————————————
1. 快速的取出中位数。
我:额。。排序?我毫无头绪。(唉,编程之美一定要看啊。)
面试官:快速排序啊,这个问题可以转化为找出第k大的数,平均复杂度基本就是n。
————————————
痛定思痛,回来好好研究了一下快速排序。根据@研究者July的博客,也算是研究了几种快速找出第k大的数。
先来快速排序:
def quicksort(q):
if len(q) <= 1:
return q
else:
pivot = q[0]
return quicksort([x for x in q[1:] if x < pivot]) + [pivot] +\
quicksort([x for x in q[1:] if x >= pivot])
快速排序的算法很简单,Python写迭代甚至比伪代码都好理解。找出数组中的第一个数,然后比这个数小的放到前面,比这个数大的放到后面。然后对前后两个数组再分别快速排序。算法平均复杂度公认为O(n)
借着快速排序,我可以找到第k小的数。因为以后这个模块还可以用来做别的,因此先写一个模块,输出前k小的数。
def min_k_elements_helper(q, k):
'''
find the minimum k elements in q
not sorted
'''
if len(q) == k:
return q
elif len(q) > k:
pivot = q[0]
left_half = [x for x in q[1:] if x < pivot] + [pivot]
right_half = [x for x in q[1:] if x >= pivot]
if len(left_half) == k:
return left_half
elif len(left_half) > k:
return min_k_elements_helper(left_half, k)
else:
return left_half + min_k_elements_helper(right_half, k - len(left_half))
else:
raise
算法思维也很简单,我每次拿出一个数,比它小的排前面,比它大的排后面,然后继续分,不排序,前面那个数组的大小正好为k的时候(多分少补),返回前面那个数组即可。
然后再取出其中的最大值返回即可。
中位数的话无非加个判断,分两种情况去计算。
根据@研究者July的分析,其实维护一个k大的数组,然后每次跟其中最大的相比,小的则取代最大值存入数组。缺点是当数组很大时,计算出其中的最大值需要点时间。
然后就是最节省时间的最小堆了。维护一个k大的最小堆,顶部是最大值,一一比较。最小二叉树的机制保证了当存入一个新数值时的效率,具体不说明了。但是想要做一个k大的最小堆并不容易。使用heapq可以比较容易的实现最大堆:
class MaxkHeap(object):
def __init__(self, k):
self.k = k
self.data = []
def push(self, item):
if len(self.data) < self.k:
heapq.heappush(self.data, item)
else:
topk_small = self.data[0]
if item > self.data[0]:
heapq.heapreplace(self.data, item)
def topk(self):
'''
return the maxmum sorted elements
'''
return [item for item in reversed([heapq.heappop(self.data) for _ in xrange(len(self.data))])]
heapq可以保证顶点始终是最小的数,如果过来的数比它大,那么替换顶点。Topk是用来输出最大n个值,顺序需要转换。
去网上查了一下,有人给出了一个非常巧妙的最小堆的实现方法,那就是存入负值,然后取出的时候取反即可。
class MinkHeap(object):
def __init__(self, k):
self.k = k
self.data = []
def push(self, item):
item = -item
if len(self.data) < self.k:
heapq.heappush(self.data, item)
else:
if item > self.data[0]:
heapq.heapreplace(self.data, item)
def topk(self):
'''
return minimum k sorted elements
'''
return [-heapq.heappop(self.data) for _ in range(len(self.data))]
仔细对比一下上下两个类,就是输入和输出有一点不一样,实在是太巧妙了。
有了最小堆之后,我们又可以继续优化效率了。
def min_k_elements_with_heapq(q, k):
from top_k_heapq import MinkHeap
heap_min = MinkHeap(k)
for item in q:
heap_min.push(item)
return heap_min.topk()
数据结构,算法都是我很欠缺的东西,从今天开始要加油了。
源代码:Github