leetcode703. 数据流中的第 K 大元素
方法一 排序
使用快速排序,维护数组前K大的值,每次添加时,和数组的最小值作比较,如果加入的值大于最小值,则将数组中最小值弹出,将该值添加进去后排序,时间复杂度:O(n*klogk),空间复杂度O(k)
缺点:时间复杂度比较高。
方法二 堆
维护一个二叉堆,初始化二叉堆的时间复杂度:O(nlogk), 其中n代表初始化nums的长度。单次插入的时间复杂度为O(logk)。空间复杂度:O(k)。
实现方式1:调用heapq
库
python中的heapq
堆,堆是非线性的树形的树状结构,有小根堆和大根堆两种堆。(python标准库heapq库中的堆默认是小根堆)
- 大根堆:树中各个父节点的值总是大于或等于任何一个子节点的值。
- 小根堆:树中各个父节点的值总是小于或等于任何一个子节点的值。
如图所示:
我们一般使用二叉堆来实现优先队列,它的内部调整算法复杂度为logN。
heapq堆的常用方法:
-
heapq.heappop(heap)
删除并返回最小值,因为堆的特征是heap[0]永远是最小的元素,所以一般都是删除最小的元素。
>>> list [1, 1, 3, 5, 2, 5, 8, 9, 6] >>> heapq.heappop(list) 1 >>> list [1, 2, 3, 5, 6, 5, 8, 9]
-
heapq.heappush
(heap, item)
heap为定义堆,item为增加的元素
demo1
: 维持一个topK
的堆:
import heapq
k = 5
h = [] # 预定义的堆
lst = [2,45,65,87,854,244,24,5,73,46,78,9]
for i in lst:
heapq.heappush(h,i) # 向堆中增加元素
if len(h) > k:
heapq.heappop(h) # 向堆中减少元素
print(h)
print('该列表中第%d大的数为:' % k,h[0])
heapq.heapify
(list)
将列表转化为堆:
>>> list = [1,2,3,5,1,5,8,9,6]
>>> heapq.heapify(list)
>>> list
[1, 1, 3, 5, 2, 5, 8, 9, 6]
-
其他:
函数名 描述 heapq.heapreplace(heap.item) 删除并返回最小元素值,添加新的元素值 heapq.heappushpop(list, item) 判断添加元素值与堆的第一个元素值对比;如果大,则删除并返回第一个元素,然后添加新元素值item.如果小,则返回item. 原堆不变。 heapq.merge(…) 将多个堆合并
题目实现:
class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.heap = []
self.k = k
for num in nums:
heapq.heappush(self.heap,num)
if len(self.heap) > k:
heapq.heappop(self.heap)
def add(self, val: int) -> int:
heapq.heappush(self.heap,val)
if len(self.heap) > self.k:
heapq.heappop(self.heap)
return self.heap[0]
实现方式2:手写实现一个堆
堆的特点:
- 内部数据是有序的
- 可以弹出栈顶的元素,大顶堆就是弹出最大值,小项堆就是弹出最小值。
- 每次加入新元素或者弹出栈顶元素后,调整堆使之重新有序仅需要O(logn)的时间。
- 支持在线算法
堆的本质:
-
它是一个完全二叉树
-
实现的时候我们不需要建造一个树,改用一个数组即可
- 将完全二叉树和一个数组关联到一起:给树的节点编号,节点的编号就是元素在数组中的下标。
-
于是我们可以得出一个结论:
已知一个节点的编号为
index
,那么它的父节点的编号为:
f a t h e r _ i n d e x = ⌊ i n d e x − 1 2 ⌋ father\_index = \lfloor {index-1 \over 2}\rfloor father_index=⌊2index−1⌋
左孩子节点的编号为:
l e f t _ i n d e x = i n d e x ∗ 2 + 1 left\_index = index*2 + 1 left_index=index∗2+1
有孩子节点的编号为:
r i g h t _ i n d e x = i n d e x ∗ 2 + 2 right\_index = index*2+2 right_index=index∗2+2
如何调整堆?
- 添加元素
- 把新数据添加到数的最后一个元素,也就是数组的末尾
- 把末尾节点向上调整
- 弹出堆顶
- 交换根节点与最后一个节点的值
- 删除最后一个节点
- 把根节点向下调整
class Heap:
def __init__(self,desc=False):
"""初始化,默认创建一个小顶堆"""
self.heap = []
self.desc = desc
@property
def size(self):
return len(self.heap)
def top(self):
if self.size:
return self.heap[0]
return None
def push(self, item):
"""
添加元素
第一步,把元素加入到数组末尾
第二步,把末尾元素向上调整
"""
self.heap.append(item)
self._sift_up(self.size-1)
def pop(self):
"""
弹出栈顶
第一步:记录栈顶元素的值
第二步:交换栈顶元素与末尾元素的值
第三步:删除数组末尾的值
第四步:新的栈顶元素向下调整
第五步:返回答案
"""
item = self.heap[0]
self._swap(0,self.size-1)
self.heap.pop()
self._sift_down(0)
return item
def _smaller(self,lhs,rhs):
return lhs > rhs if self.desc else lhs < rhs
def _sift_up(self,index):
"""
向上调整
如果父节点和当前节点满足交换的关系
(对于小根堆是父节点元素更大,对于大根堆是父节点更小)
则持续将当前节点向上调整
"""
while index:
parent = (index-1) // 2
if self._small(self.heap[parent],self.heap[index]):
break
self._swap(parent,index)
index = parent
def _sift_down(self,index):
"""
向下调整
如果子节点和当前节点满足交换的关系
则持续将当前节点向下调整
"""
while index*2+1 < self.size:
smallest = index
left = index*2+1
right = index*2+2
if self._smaller(self.heap[left],self.heap[smallest]):
smallest = left
if right < self.size and self._smaller(self.heap[right],self.heap[smallest]):
smallest = right
if smallest == index:
break
self._swap(index,smallest)
index = smallest
def _swap(self,i,j):
self.heap[i],self.heap[j] = self.heap[j],self.heap[i]
class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.heap = Heap()
self.k = k
for num in nums:
self.heap.push(num)
if self.heap.size > k:
self.heap.pop()
def add(self, val: int) -> int:
self.heap.push(val)
if self.heap.size > self.k:
self.heap.pop()
return self.heap.top()
参考来源:
python中heapq堆的讲解https://blog.csdn.net/qq_35883464/article/details/99410423
leetcode 题解:https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/solution/python-dong-hua-shou-xie-shi-xian-dui-by-ypz2/