Notion에서 작성한 글이라, 여기 에서 더 깔끔하게 보실 수 있습니다! 😮😊
[Kth Largest Element in a Stream]
개인적으로 오늘의 비기너, 미들러, 챌린저 문제 중 가장 어려웠다. 왜 어렵게 느꼈는지 돌아보면, ① 반복적으로 번째로 큰 원소를 효율적으로 return시킬 발상이 잘 떠오르지 않았고, ② 클래스 사용이 미숙했다.
add()
from heapq import *
class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.heap = nums
self.k = k
heapify(self.heap)
while self.heap and len(self.heap) > k:
heappop(self.heap)
def add(self, val: int) -> int:
if len(self.heap) < self.k:
heappush(self.heap, val)
return self.heap[0]
if val <= self.heap[0]:
return self.heap[0]
heappushpop(self.heap, val)
return self.heap[0]
add()
하려는 값이 힙의 최솟값보다 작거나 같다는 것은, 번째로 큰 원소가 바뀌지 않는다는 의미이므로 그대로 힙의 최솟값을 return한다.add()
하려는 값이 힙의 최솟값보다 크다는 것은 번째로 큰 원소가 바뀐다는 의미이므로, 힙에서 최솟값을 pop한 다음 push해 준다. (이 경우에서는 push를 먼저 하고 pop을 해도 상관없으므로, heappushpop
을 이용했다.)nums
로 힙이 구성되는 경우였다. Constraints에서 번째 원소를 찾을 때 개의 원소가 있다는 것이 보장된다고 했는데 왜 그런 건지 고민했는데, push를 해야 개가 되는 경우가 문제였다. 항상 경계 조건에 유의해야 겠다.from heapq import *
from collections import defaultdict
def solution(operations):
def sync_remove(heap, n):
while heap and not log[-n*heap[0]]: heappop(heap)
if heap: log[-n*heappop(heap)] -= 1
mx = []
mn = []
log = defaultdict(int)
for o in operatios:
c, n = o.split()
n = int(n)
if c == 'I':
heappush(mx, -n)
heappush(mn, n)
log[n] += 1
else: sync_remove(mx if n==1 else mn, n)
while mx and not log[-mx[0]]: heappop(mx)
while mn and not log[mn[0]]: heappop(mn)
return [-mx[0], mn[0]] if mx else [0, 0]
heapq
를 이용하여 최솟값과 최댓값을 한 번에 관리할 수는 없다. 따라서, min-heap과 max-heap을 따로 관리하여야 한다.
여기서, "D 1"
로 최댓값을 삭제하는 상황을 생각해 보자. max-heap에서 heappop()
을 해 주고, min-heap에서는 어떻게 해야 할까?
heappop()
을 할 경우 최솟값이 삭제되는 것이고, ( X )remove()
를 이용할 경우 매 삭제마다 이 소요된다. ( X )→ 힙에서 삭제 연산을 진행할 때,삭제 과정의 동기화를 위해 무슨 원소를 삭제했었는지 기록을 남겨놓고,
heappop()
을 이용하여 유효한 값이 나올 때까지 삭제함으로써 두 힙을 동기화시켜준다.이를 위해 값을 찾는 데 Amortized 이 소요되는 dictionary를 이용했다.
I $n$
(삽입)
“D 1”
(최댓값 삭제)
“D -1”
(최솟값 삭제)
return
from heapq import *
heap = []
for _ in range(int(input())):
x = int(input())
if x: heappush(heap, -x)
else: print(-heappop(heap) if heap else 0)