2024. 5. 25. 23:25ㆍ개발의 흔적/코딩테스트
https://leetcode.com/problems/smallest-number-in-infinite-set/description/


오늘의 문제,
SmallestIntInfiniteSet에서 자연수로 이루어진 Set를 구현하고 popSmallest(가장 작은값 pop), addBack(값 추가) function을 구현하기
(문제에서는 짤렸지만, 1<=<=1000 이 contraint 이며, testcase에서 definition의 total call 수는 at most 1000이다.)
popSmallest : heap 자료구조를 사용하면 효율적일 것 (O(lgN))
addBack : 'num'이 현재 존재하지 않는다면 push
heappush를 사용하는 것이 코드길이 및 시간복잡도상 효율적일 것인데 (O(lgN)),
'num'의 존재여부를 탐색하는데에는 hash (dictionary or set)이 적절 할 것이다. (O(1))
이런 생각하에 우선 부랴부랴 작성해본 코드
import heapq as hq
class SmallestInfiniteSet:
def __init__(self):
self.num_set = {i for i in range(1, 1001)} #O(N)
heap = [i for i in range(1, 1001)] #O(N)
hq.heapify(heap)
self.heap = heap
def popSmallest(self) -> int:
if len(self.heap) == 0:
return None
popped = hq.heappop(self.heap)
if len(self.heap) == 0:
return popped # O(lgN)
self.num_set.remove(popped)
return popped
def addBack(self, num: int) -> None:
if num not in self.num_set: # O(1)
self.num_set.add(num) # O(lgN)
hq.heappush(self.heap, num)
그냥 call이 1000번 발생한다고 생각했을때에,
__init__ : 맨 처음 객체 선언 할때에 한 번 call 총 O(2N) = O(N)이긴 하나, 확정적으로 2N(2000)번 연산 발생
popSmallest & addBack : 총 1000번 call -> 1000lg(1000) 의 연산 ~= 10,000번
대략 12,000번의 연산
...
만족스러운 연산량이라 생각은 하나,
다른 문제 해결자에 비해 그렇게 효율적인 runtime과 memory를 가져오지는 못한듯...
그래서 문제를 다시 생각해보고 또 타 풀이를 보니, 굳이 직접 숫자를 자료구조에 넣을 필요가 없었다...
class 이름은 SamllestInfiniteSet, 즉 애초에 모든 수를 관리 할 수 있어야함
가장작은 숫자를 self.root=1로 initilize하고 heap과 set을 선언한다.
root : 가장 작은 값 추적
그러나 이전의 나와는 달리,
heap : 제거되었다가 추가된 숫자들을 관리
set : heap에 있는 숫자들을 그대로 포함. (위의 나처럼 탐색에서의 효율을 위해 선언)
import heapq as hq
class SmallestInfiniteSet2:
def __init__(self):
self.root = 1
self.min_heap = []
self.in_heap = set()
def popSmallest(self) -> int:
if self.min_heap:
smallest = hq.heappop(self.min_heap)
self.in_heap.remove(smallest)
else:
smallest = self.root
self.root += 1
return smallest
def addBack(self, num: int) -> None:
if num < self.root and num not in self.in_heap:
hq.heappush(self.min_heap, num)
self.in_heap.add(num)
popSmallest : min_heap이 채워져있다면, (즉, 제거되었다가 추가된 숫자가 있다면) heappop을 반환
min_heap이 없다면, root를 반환하고 root +=1 (이는 제거 될 수 있는 수는 1부터 시작해서 연속된 수 밖에 없기 때문이다. 이게 이 풀이의 keyPoint인듯)
addBack : 현재 root보다 작고, 추가된적 없는 숫자라면, heap 및 set에 추가
이 두 함수에서 결국 heap의 탐색기능을 쓰므로, 위의 내 코드처럼 O(lg(N))의 시간복잡도를 가지지만,
실질적으로 heap에 들어가있는 숫자가 현저히 적고, 정말 모든 숫자에 대해서 적용가능하므로 효율적이다.
그리고 init시의 연산이 1이므로, 약간의 효율성도 가져 올 수 있음


추가적으로 외울 코드들
'개발의 흔적 > 코딩테스트' 카테고리의 다른 글
| [완전탐색] 문제에서 '완전탐색일 수 밖에 없는 이유' 찾기 (0) | 2024.05.29 |
|---|---|
| [완전탐색] 중학교 수학으로 빠르게 코딩하기 (0) | 2024.05.28 |
| [Heap] heapq의 시간복잡도(1) (0) | 2024.05.24 |
| [Hash] Dictionary 시간복잡도 (2) - 시간초과 예시 (0) | 2024.05.22 |
| [Hash] Dictionary의 시간복잡도 (0) | 2024.05.20 |