本文内容纯干货,假设读着有一定的基础,重在总结。整体行文逻辑如下:
快排的思想随便找本书就可以找到,二分思想,递归实现。
算法理解起来简单,但在面试时想优雅的写出来却不容易。下面是我见过的比较优雅的python实现:
def partition(arr, i, j):
if i >= j:
return
loc, start, end = i, i, j
while i < j:
while i < j and arr[j] >= arr[loc]: j -= 1
while i < j and arr[i] <= arr[loc]: i += 1
if i < j:
arr[i], arr[j] = arr[j], arr[i]
arr[i], arr[loc] = arr[loc], arr[i]
partition(arr, start, i - 1)
partition(arr, i + 1, end)
arr = [3,1,4,5,7,2,1,4,5,0]
partition(arr, 0, len(arr) - 1)
print(arr)
实现中有几点需要注意:
这个问题被面试问到的概率非常大,几乎和未来一周可能会下雨的概率接近。下面列出几种常见解法及说明:
先降序排序,再取第K个:排序时间复杂度最优是O(NlogN),取第K个是O(1),因此平均时间复杂度是O(NlogN)。代码略。
使用小顶堆,维护topK,最后取堆顶元素:小顶堆的维护是O(logK),虽然在遍历N的过程中可以通过条件判断,减少小顶堆的维护,实际平均时间复杂度是O(NlogK)。code如下:
import heapq
def getK(arr, K):
min_hq = []
for v in arr:
if len(min_hq) < K or min_hq[0] < v:
heapq.heappush(min_hq, v)
if len(min_hq) > K:
heapq.heappop(min_hq)
return min_hq[0]
arr = [3, 1, 4, 5, 7, 2, 1, 4, 5, 0]
print(getK(arr, 3))
前面的解法平均到O(NlogK)了,我们是否可以期待平均时间复杂度更低的算法,如O(N)? yes.
利用快排的思想,找锚点,数据降序移动到两侧,看锚点是否是第K个,如果锚点靠左则去右侧递归查找,反之亦然。
复杂度???
平均时间复杂度应该是这样一个式子:O(N)+O(N/2)+O(N/4)+O(N/8)+....
所以平均时间复杂度是O(N)
牛逼哥拉斯!那它是最好的吗?no, 我们只看到了平均情况,但它有最坏情况。
如:数据是升序的,假设每次锚点都取最左侧,那每次递归只缩小了1个数据的范围,相应时间复杂度会到O(N*(N-K))
类似的如果是降序的,锚点仍然在最左侧,每次只扩大了1个数据范围,时间复杂度会是O(N*K)
talk is cheap, show code:
def getK(arr, i, j, K):
# if i >= j:
# return None
loc, start, end = i, i, j
while i < j:
while i < j and arr[j] <= arr[loc]: j -= 1
while i < j and arr[i] >= arr[loc]: i += 1
if i < j:
arr[i], arr[j] = arr[j], arr[i]
arr[i], arr[loc] = arr[loc], arr[i]
if i + 1 == K:
return arr[i]
elif i + 1 > K:
return getK(arr, start, i - 1, K)
else:
return getK(arr, i + 1, end, K)
arr = [3, 1, 4, 5, 7, 2, 1, 4, 5, 0]
# print(getK(arr, 0, len(arr) - 1, 1))
print(getK(arr, 0, len(arr) - 1, 3))
# print(getK(arr, 0, len(arr) - 1, 4))
# print(getK(arr, 0, len(arr) - 1, 6))
综上: