diff --git a/course-schedule/gyeo-ri.py b/course-schedule/gyeo-ri.py new file mode 100644 index 0000000000..f274c83f89 --- /dev/null +++ b/course-schedule/gyeo-ri.py @@ -0,0 +1,113 @@ +""" +[결과 요약] +# 시도한 로직 수: 2 + 1. 위상 정렬로 푸는 방법 + - 시간복잡도 O(n) / 공간복잡도 O(n) + - 실제로는 numCourses + len(prerequisites) 만큼의 복잡도 (O(V+E)) + - 원리: 모든 노드를 정렬할 수 있는지 체크(순환이 있으면 모든 노드 정렬 불가) + 2. DFS를 활용하는 방법 + - 1번과 동일하게 O(V+E) + - 원리: 탐색(비순환일때만 가능)을 완료할 수 있는지 체크해서 순환 여부를 판단 + +""" + + +class Solution: + def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool: + # 3. DFS 함수 정의 + def dfs(current: int) -> bool: + match course_states[current]: + case 2: # 탐색 완료 + return True + case 1: # 탐색 중 + return False + case 0: # 탐색 전 + course_states[current] = 1 + for next_ in course_graph[current]: + if not dfs(next_): + return False + + course_states[current] = 2 + return True + + # 1. 인덱스가 0부터 시작하는 그래프 초기화 + course_graph: list[list[int]] = [[] for _ in range(numCourses)] + for after, before in prerequisites: + course_graph[before].append(after) + + # 2. 각 노드별 탐색 상태를 표시하는 객체 + course_states: list[int] = [0] * numCourses + + # 3. 모든 노드를 1회씩 순회하면서 dfs + for course in range(numCourses): + if not dfs(course): + return False + return True + + +""" +# 위상정렬 +from collections import deque + +class Solution: + def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool: + # 1-a. 인덱스가 0부터 시작하는 그래프 초기화 + course_graph: list[list[int]] = [[] for _ in range(numCourses)] + + # 1-b. 각 노드별 선행 조건 갯수 리스트 + required_courses: list[int] = [0] * numCourses + + # 2. 선수과목을 돌면서 그래프를 채우기 + # Prerequisite이 [A, B]이면 A(After)를 수강하기 전에 B(Before)가 필요 + for after, before in prerequisites: + course_graph[before].append(after) + required_courses[after] += 1 + + # 3. 현재 수강 가능한 강의 체크 + available_courses = deque() + for i in range(numCourses): + if required_courses[i] == 0: + available_courses.append(i) + + # 4. queue가 빌 때까지 + done = 0 + while available_courses: + + # 수강 가능한 강의를 하나 꺼내서 수강 처리 + current = available_courses.popleft() + done += 1 + + # 이 강의의 선행 강의 찾기 + for next_ in course_graph[current]: + required_courses[next_] -= 1 + + if required_courses[next_] == 0: + available_courses.append(next_) + + return done == numCourses +""" + + +if __name__ == "__main__": + + test_cases = [ + (2, [[1, 0]], True), + (2, [[1, 0], [0, 1]], False), + (3, [], True), + (4, [[1, 0], [2, 1], [3, 2]], True), + (5, [[1, 0], [2, 0], [3, 1], [3, 2], [4, 3]], True), + (1, [[0, 0]], False), + (4, [[1, 0], [2, 1], [3, 2], [1, 3]], False), + ] + + solution = Solution() + + for idx, (numCourses, prerequisites, expected) in enumerate(test_cases, start=1): + + result = solution.canFinish(numCourses, prerequisites) + + assert ( + result == expected + ), f"Test Case {idx} Failed: Expected {expected}, Got {result}" + + print("All test cases passed.") diff --git a/invert-binary-tree/gyeo-ri.py b/invert-binary-tree/gyeo-ri.py new file mode 100644 index 0000000000..4c8b85a499 --- /dev/null +++ b/invert-binary-tree/gyeo-ri.py @@ -0,0 +1,132 @@ +""" +[결과 요약] +# 시도한 로직 수: 2 + 1. DFS를 활용하는 방법 + - 현재 노드의 좌우를 바꾼 다음 바로 아래 노드로 이동 + - 하위 노드가 없는 노드를 만날 때까지 반복되고 종료됨 + - 시간복잡도는 O(n) + - 공간복잡도는 O(n)이고, 트리가 균형이면 O(logn)까지 가능(B-Tree 같은 경우) + 2. BFS를 활용하는 방법 + - 시간복잡도/공간복잡도 모두 O(n) + - DFS와 성능 차이가 크게 없음 + 3. BFS에서 약간의 메모리 개선하기 + - None을 queue에 넣지 않는 방법 + - 트리 크기가 아주 크지 않다면 큰 효과는 없음 +""" + +from collections import deque + + +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: TreeNode | None) -> TreeNode | None: + if not root: + return root + + next_nodes: deque[TreeNode] = deque([root]) + + while next_nodes: + current = next_nodes.popleft() + if not current: + continue + + current.left, current.right = current.right, current.left + + if current.left: + next_nodes.append(current.left) + if current.right: + next_nodes.append(current.right) + + return root + + +""" +# DFS를 활용한 방법 +class Solution: + def invertTree(self, root: TreeNode | None) -> TreeNode | None: + def dfs(current: TreeNode | None): + if not current: + return + + current.left, current.right = current.right, current.left + + dfs(current.right) + dfs(current.left) + + dfs(root) + return root +""" + +if __name__ == "__main__": + from collections import deque + + def _build_tree(values: list[int | None]) -> TreeNode | None: + if not values: + return None + + nodes = [TreeNode(value) if value is not None else None for value in values] + + child_idx = 1 + + for node in nodes: + if node is not None: + if child_idx < len(nodes): + node.left = nodes[child_idx] + child_idx += 1 + + if child_idx < len(nodes): + node.right = nodes[child_idx] + child_idx += 1 + + return nodes[0] + + def _tree_to_list(root: TreeNode | None) -> list[int | None]: + if root is None: + return [] + + result = [] + queue = deque([root]) + + while queue: + node = queue.popleft() + + if node is None: + result.append(None) + continue + + result.append(node.val) + queue.append(node.left) + queue.append(node.right) + + while result and result[-1] is None: + result.pop() + + return result + + test_cases = [ + ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), + ([2, 1, 3], [2, 3, 1]), + ([], []), + ([1], [1]), + ([1, 2], [1, None, 2]), + ] + + solution = Solution() + + for idx, (inp, expected) in enumerate(test_cases, start=1): + root = _build_tree(inp) + + result_root = solution.invertTree(root) + result = _tree_to_list(result_root) + + assert ( + result == expected + ), f"Test Case {idx} Failed: Expected {expected}, Got {result}" + + print("All test cases passed.")