diff --git a/course-schedule/haklee.py b/course-schedule/haklee.py new file mode 100644 index 000000000..fd40fbdac --- /dev/null +++ b/course-schedule/haklee.py @@ -0,0 +1,31 @@ +"""TC: O(node + edge), SC: O(node + edge) + +유명한 위상 정렬 알고리즘이므로 설명은 생략한다. +""" + + +class Solution: + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + # 위상 정렬. + + # init + adj_list = [[] for _ in range(numCourses)] # SC: O(edge) + in_deg = [0] * numCourses # SC: O(node) + + for edge in prerequisites: + adj_list[edge[0]].append(edge[1]) + in_deg[edge[1]] += 1 + + node_to_search = [i for i, v in enumerate(in_deg) if v == 0] # TC: O(node) + sorted_list = [] + + # process + while node_to_search: + cur = node_to_search.pop() # TC: 최악의 경우 총 O(node)만큼 실행 + sorted_list.append(cur) + for node in adj_list[cur]: + in_deg[node] -= 1 # TC: 최악의 경우 총 O(edge)만큼 실행 + if in_deg[node] == 0: + node_to_search.append(node) + + return len(sorted_list) == numCourses diff --git a/invert-binary-tree/haklee.py b/invert-binary-tree/haklee.py new file mode 100644 index 000000000..0b8b3e58e --- /dev/null +++ b/invert-binary-tree/haklee.py @@ -0,0 +1,32 @@ +"""TC: O(n), SC: O(h) + +h는 이진 트리의 높이. +n이 전체 노드 개수라고 할때 +- 최악의 경우 한 쪽 자식 노드만 채워짐. 이 경우 h = n. +- 최선의 경우 완전 이진 트리. h = log(n). + +아이디어: +양쪽 자식 노드에 접근해서 재귀적으로 invert를 진행하고, 두 자식 노드를 바꾼다. + +SC: +- 호출 스택 깊이는 트리의 깊이까지 깊어질 수 있다. 즉, O(h). + +TC: +- 모든 노드에 접근. O(n). +""" + + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: + def invert(node: Optional[TreeNode]) -> None: + if node is not None: + node.left, node.right = invert(node.right), invert(node.left) + return node + + return invert(root) diff --git a/jump-game/haklee.py b/jump-game/haklee.py new file mode 100644 index 000000000..18029c522 --- /dev/null +++ b/jump-game/haklee.py @@ -0,0 +1,44 @@ +"""TC: O(n), SC: O(1) + +n은 주어진 리스트의 길이 + +아이디어: +- 끝에 있는 아이템부터 시작해서 '최소 어디까지는 도달해야 끝 칸까지 점프 가능한지'를 업데이트 한다. +- example들로 이해해보자. index는 0부터 시작. + - example 1: [2,3,1,1,4] + - 4번째 칸에 도달할 수 있으면 성공이다. reach_at_least 값을 4로 초기화 한다. + - 3번째 칸에서는 최대 4번째 칸까지 갈 수 있다. 즉, 적어도 3번 칸까지 가면 성공이므로 + reach_at_least를 3으로 업데이트 한다. + - 2번째 칸에서는 최대 3번째 칸까지 갈 수 있다. reach_at_least를 2로 업데이트 한다. + - 1번째 칸에서는 최대 1+3=4번째 칸까지 갈 수 있다. 이 칸에서 현 reach_at_least 값인 2번째 칸까지 + 충분히 갈 수 있으므로 reach_at_least 값을 1로 업데이트 한다. + - 0번째 칸에서는 최대 0+2=2번째 칸까지 갈 수 있다. 현 reach_at_least 값인 1번째 칸까지 충분히 + 갈 수 있으므로 reach_at_least 값을 0으로 업데이트 한다. + - 0번째 칸에서 끝 칸까지 갈 수 있다. + - example 2: [3,2,1,0,4] + - 4번째 칸에 도달할 수 있으면 성공이다. reach_at_least 값을 4로 초기화 한다. + - 3번째 칸에서는 최대 3번째 칸까지 갈 수 있다. 여기서는 현 reach_at_least 값인 4까지 갈 수 없으니 + 아무 일도 일어나지 않는다. + - 2번째 칸에서는 최대 2+1=3번째 칸까지 갈 수 있다. 여기서도 현 reach_at_least 값인 4까지 갈 수 없고, + 아무 일도 일어나지 않는다. + - 1번째 칸에서는 최대 1+2=3번째 칸까지 갈 수 있다. 비슷하게 아무 일도 일어나지 않는다. + - 0번째 칸에서는 최대 0+3=3번째 칸까지 갈 수 있다. 비슷하게 아무 일도 일어나지 않는다. + - reach_at_least 값이 0이 아니다. 즉, 0번째 칸에서는 끝 칸까지 갈 수 없다. + +SC: +- reach_at_least 값에 인덱스 하나만 관리한다. 즉, O(1). + +TC: +- nums의 끝에서 두 번째 아이템부터 첫 번째 아이템까지 순차적으로 접근하면서 reach_at_least값을 업데이트 한다. O(n). +""" + + +class Solution: + def canJump(self, nums: List[int]) -> bool: + reach_at_least = len(nums) - 1 + + for i in range(len(nums) - 2, -1, -1): + if nums[i] + i >= reach_at_least: + reach_at_least = i + + return reach_at_least == 0 diff --git a/merge-k-sorted-lists/haklee.py b/merge-k-sorted-lists/haklee.py new file mode 100644 index 000000000..33ce6818e --- /dev/null +++ b/merge-k-sorted-lists/haklee.py @@ -0,0 +1,56 @@ +"""TC: O(n*log(l)), SC: O(l) + +l은 리스트 개수, n은 전체 아이템 개수 + +아이디어: +- 각 리스트에서 제일 앞에 있는 값을 뽑아서 우선순위 큐에 넣는다. +- 우선순위 큐의 제일 앞에 있는 값을 뽑아서 + - 이 값이 어느 리스트에서 나왔는지 확인해서 해당 리스트의 제일 앞에 있는 값을 새로 뽑아서 우선순위 큐를 채운다. + - 우선순위 큐에서 뽑은 값은 결과 리스트에 더한다. + +SC: +- 우선순위 큐에 최대 list의 개수 만큼의 아이템 존재 가능. O(l). +- + +TC: +- heap 크기는 최대 l이다. +- 이 heap에 아이템을 push하고 pop할때 O(log(l)) 시간 소요. +- 위의 시행을 전체 아이템 개수 만큼 한다. +- 종합하면 O(n*log(l)) +""" + +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next + +from heapq import heappush, heappop + + +class Solution: + def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: + heap = [] + head = ListNode() + tail = head + + # init + for i in range(len(lists)): + if lists[i]: + heappush(heap, (lists[i].val, i)) + lists[i] = lists[i].next + + # process + while heap: + v, idx = heappop(heap) + + # heap 다시 채워넣기 + if lists[idx]: + heappush(heap, (lists[idx].val, idx)) + lists[idx] = lists[idx].next + + # 결과물 채워넣기 + tail.next = ListNode(v) + tail = tail.next + + return head.next diff --git a/search-in-rotated-sorted-array/haklee.py b/search-in-rotated-sorted-array/haklee.py new file mode 100644 index 000000000..137bc9292 --- /dev/null +++ b/search-in-rotated-sorted-array/haklee.py @@ -0,0 +1,53 @@ +"""TC: O(n), SC: O(1) + +n은 주어진 리스트의 길이. + +아이디어: +- Rotated Sorted Array는 특성상 `값이 증가하다가 -> 갑자기 값이 한 번 감소 -> 이후 다시 쭉 증가`한다. +- 위의 관찰에 따르면 값이 감소하는 `절점`은 최대 한 군데 있을 수 있다. + - rotate 시행을 0번 한 경우 절점이 없음. 그 외에는 절점이 한 번 생김. +- 즉, 리스트에서 두 구간을 겹치지 않게 잡으면 이 두 구간 중 적어도 한 구간은 ascending order가 보장된다. +- ascending하는 구간에 찾고자 하는 값이 있는지 판별하는 방식으로 binary search와 비슷한 방식으로 search 가능. +- 자세한 내용은 코드를 참조하면 된다. + +SC: +- binary search와 비슷하게, 탐색 구간의 시작, 끝, 중간 인덱스를 관리. O(1). + +TC: +- binary search와 비슷하게 구간이 계속 절반 크기로 줄어든다. O(log(n)). +""" + + +class Solution: + def search(self, nums: List[int], target: int) -> int: + s, e = 0, len(nums) - 1 + while s < e: + m = (s + e) // 2 + + # 절점은 하나다. [s, m]과 [m+1, e]구간 중 한 곳에 절점존재. + # 절점이 없는 구간은 ascending order가 보장되므로, + # 이 구간에 target이 있는지 여부로 둘 중 한 구간을 탐색 구간에서 제외한다. + if nums[s] < nums[m]: + # [s, m] 구간이 ascending order. + if (nums[s] > target and nums[m] > target) or ( + nums[s] < target and nums[m] < target + ): + # nums[s]와 nums[m]이 target보다 둘 다 크거나 둘 다 작으면 + # [m + 1, e] 구간에서 탐색을 이어간다. + s = m + 1 + else: + # 아니면 [s, m] 구간에서 탐색을 이어간다. + e = m + else: + # [m + 1, e] 구간이 ascending order. + if (nums[m + 1] > target and nums[e] > target) or ( + nums[m + 1] < target and nums[e] < target + ): + # nums[m + 1]과 nums[e]가 target보다 둘 다 크거나 둘 다 작으면 + # [s, m] 구간에서 탐색을 이어간다. + e = m + else: + # 아니면 [m + 1, e] 구간에서 탐색을 이어간다. + s = m + 1 + + return s if nums[s] == target else -1