diff --git a/course-schedule/liza0525.py b/course-schedule/liza0525.py new file mode 100644 index 0000000000..e8c9d68a33 --- /dev/null +++ b/course-schedule/liza0525.py @@ -0,0 +1,48 @@ +from collections import defaultdict + +# 7기 풀이 +# 시간 복잡도: O(V + E) +# - numCourses(V)와 prerequisites의 길이(E)에 비례 +# 공간 복잡도: O(V) +# - 각 과목의 상태를 계산하는 state와 prereq_map의 공간은 numCourses(V)에 비례 + +BEFORE_START = 0 +IN_PROGRESS = 1 +DONE = 2 +class Solution: + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + # states: 각 과목의 상태 저장, index가 곧 과목을 나타냄 + states = [BEFORE_START] * numCourses + + # 선수과목(key) 당 다음 과목들(value) 정보 저장 + prereq_map = defaultdict(list) + + for next_course, pre_course in prerequisites: + prereq_map[pre_course].append(next_course) + + def dfs(course): + if states[course] != BEFORE_START: + # 이미 들었거나, 듣는 중이라면 더이상 탐색하지 않아도 됨 + return + + states[course] = IN_PROGRESS # 현재 과목을 듣는 중 상태로 변경 + + next_courses = prereq_map.get(course, []) # 다음 과목의 정보 가져오기 + + for next_course in next_courses: + # 다음 과목들도 모두 돌아본다 + if states[next_course] == IN_PROGRESS: + # 다음 과목을 듣고 있는 중이라면, 서로가 선수과목이 된다는 이야기(그래프의 사이클이 생김) + # 이 때는 더이상 탐색하지 않고 return한다. + return + # 다음 과목에 대한 탐색 + dfs(next_course) + + # 다음 과목들도 다 들었다고 하면 사이클이 없으므로 해당 과목도 들었음 상태로 변경 + states[course] = DONE + + + for course in range(numCourses): + dfs(course) + + return IN_PROGRESS not in states # 듣는 중 상태의 존재 여부를 return diff --git a/find-minimum-in-rotated-sorted-array/liza0525.py b/find-minimum-in-rotated-sorted-array/liza0525.py index cc67e1adb8..fd74989f1e 100644 --- a/find-minimum-in-rotated-sorted-array/liza0525.py +++ b/find-minimum-in-rotated-sorted-array/liza0525.py @@ -63,3 +63,35 @@ def findMinBinarySearch(self, nums: List[int]) -> int: # # 파이썬의 내장함수인 min을 이용해도 문제 풀이 가능 # # https://wiki.python.org/moin/TimeComplexity애 의하면 min 함수도 O(n)임 # return min(nums) + + +# 7기 풀이 +# 시간 복잡도: O(log n) +# - binary search를 이용했기 때문에 최악의 경우는 log n +# 공간 복잡도: O(1) +# - 몇 가지 변수만 사용됨 +class Solution: + def findMin(self, nums: List[int]) -> int: + # 양 끝을 먼저 잡아준다 + left, right = 0, len(nums) - 1 + + # left보다 right가 클 때 계속 루프를 돈다 + # 이는 left와 right가 같아질 때까지 돈다는 말과 동일하다 + while left < right: + # 중간 index 계산 + mid = (left + right) // 2 + + if nums[mid] > nums[right]: + # 중간 index의 값이 오른쪽 index의 값보다 크다는 것은 + # 최소 값이 mid 기준 오른쪽 구역에 존재하기 때문에 left을 mid쪽으로 움직인다. + # mid의 값 자체는 최소값이 될 수 없기 때문에 left는 이보다 한 칸 옆으로 움직여야 함 + left = mid + 1 + else: + # 중간 index의 값이 오른쪽 index의 값보다 작다는 것은 + # 최소 값이 mid 기준 왼쪽 구역에 존재하기 때문에 right를 mid쪽으로 움직인다. + # mid의 값 자체는 최소값이 될 수 있기 때문에 right는 mid 값을 할당한다. + right = mid + + # left와 right가 같아질 때(최소값을 찾았을 때) 루프 탈출을 하기 때문에 + # nums[left] 또는 nums[right]를 반환 + return nums[left] diff --git a/invert-binary-tree/liza0525.py b/invert-binary-tree/liza0525.py new file mode 100644 index 0000000000..04293075ce --- /dev/null +++ b/invert-binary-tree/liza0525.py @@ -0,0 +1,20 @@ +# 7기 풀이 +# 시간 복잡도: O(n) +# - 노드의 개수(n)만큼 시간 소요 +# 공간 복잡도: O(h) +# - 주어진 트리의 높이(h)만큼 재귀 스택 사용 +class Solution: + def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: + def invert_binary_tree(node): + if not node: + return + + # 트리 탐색 시 right -> left 순서로 순회하도록 재귀 호출 + invert_binary_tree(node.right) + invert_binary_tree(node.left) + + # node의 right와 left를 서로 바꿔준다. + node.right, node.left = node.left, node.right + + invert_binary_tree(root) + return root diff --git a/jump-game/liza0525.py b/jump-game/liza0525.py new file mode 100644 index 0000000000..ba5b9ac50e --- /dev/null +++ b/jump-game/liza0525.py @@ -0,0 +1,22 @@ +# 7기 풀이 +# 시간 복잡도: O(n) +# - nums의 길이(n) 만큼 시간 소요 +# 공간 복잡도: O(1) +# - 변수(max_reach)만 사용 +class Solution: + def canJump(self, nums: List[int]) -> bool: + max_reach = 0 # 매 순간에 가장 멀리 갈 수 있는 index 저장 + + for i in range(len(nums)): + if i > max_reach: + # 현재의 인덱스가 max_reach보다 크면, 도달 자체를 할 수 없음을 의미 + # 따라서 False로 early return + return False + + # 기존의 max_reach와 i에서 최대로 갈 수 있는 거리를 계산한 값을 비교하여 + # 더 큰값으로 max_reach 업데이트 + max_reach = max(max_reach, i + nums[i]) + + # for문을 모두 돌았다는 것은 맨 끝 인덱스에 도달 가능하다는 것을 의미 + # True로 리턴한다. + return True diff --git a/merge-k-sorted-lists/liza0525.py b/merge-k-sorted-lists/liza0525.py new file mode 100644 index 0000000000..1ebcb8f9e6 --- /dev/null +++ b/merge-k-sorted-lists/liza0525.py @@ -0,0 +1,32 @@ +import heapq + +# 7기 풀이 +# 시간 복잡도: O(n log k) +# - while문의 매 루프마다 최소힙 계산(리스트의 개수 k에 입각) * 전체 노드 수(n) 만큼 최대 시간복잡도 나옴 +# 공간 복잡도: O(k) +# - 리스트의 개수(k) 만큼의 heap 사이즈가 나옴 +class Solution: + def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: + dummy = ListNode() + head = dummy # 초기 head를 dummy로 지정 + + heap = [] # 최소힙 계산을 위한 heap + + # 먼저 힙에 노드를 push + for i, node in enumerate(lists): + if node: + # heap push 기준은 node.val과 node가 위치한 index를 기준으로 + heapq.heappush(heap, (node.val, i, node)) + + # heap의 모든 노드를 pop할 때까지 while 루프 돌린다 + while heap: + _, i, node = heapq.heappop(heap) # 최소값이 들어있는 node를 pop + head.next = node # head의 next에 현재의 node를 저장 후 + head = head.next # head 다음 노드로 변경 + + if node.next: + # node에 next 노드가 있었다면 해당 노드의 val을 기준으로 heap에 push한다 + heapq.heappush(heap, (node.next.val, i, node.next)) + + # dummy 노드였으므로 next node를 return + return dummy.next diff --git a/search-in-rotated-sorted-array/liza0525.py b/search-in-rotated-sorted-array/liza0525.py index 18fbf3f0d4..24031f06e9 100644 --- a/search-in-rotated-sorted-array/liza0525.py +++ b/search-in-rotated-sorted-array/liza0525.py @@ -1,3 +1,5 @@ +# NOTE: 문제 위치를 잘못 잡음 4주차 find-minimum-in-rotated-sorted-array로 옮겨감 +# 삭제는 추후에(이유: github 리뷰 시 diff가 생겨 리뷰가 불편해질 수 있음) # 7기 풀이 # 시간 복잡도: O(log n) # - binary search를 이용했기 때문에 최악의 경우는 log n @@ -28,3 +30,45 @@ def findMin(self, nums: List[int]) -> int: # left와 right가 같아질 때(최소값을 찾았을 때) 루프 탈출을 하기 때문에 # nums[left] 또는 nums[right]를 반환 return nums[left] + + +# 7기 풀이 +# 시간 복잡도: O(log n) +# - binary search를 하므로 nums의 길이(n)의 로그 값 만큼의 시간 소요 +# 공간 복잡도: O(1) +# - 변수 몇 개만 사용 +class Solution: + def search(self, nums: List[int], target: int) -> int: + left, right = 0, len(nums) - 1 # 양 끝을 left, right로 설정 + + while left <= right: + mid = (left + right) // 2 # 중간 값 설정 + + if target == nums[mid]: + # 중간 값이 곧 target값이면 중간 index를 return + return mid + elif nums[left] <= nums[mid]: + # left ~ mid 사이가 정렬이 되어 있다면, + # (비고: 문제 조건 상, sorted list에서 rotate한 상황이므로 + # left와 mid만 비교해도 내부는 sorted 되어 있음 + # 이는 right ~ mid 비교 때와 동일함) + if nums[left] <= target < nums[mid]: + # target이 오름차순이 되어 있는 구간에 있는지 확인 + # 맞다면 right를 변경 + right = mid - 1 + else: + # 오름차순 쪽이 아닌 곳에 있다면 rotate되어 순서가 뒤틀린 쪽에 있다는 의미라 + # left를 변경 + left = mid + 1 + elif nums[mid] <= nums[right]: + # mid ~ right 사이가 정렬이 되어 있다면, + # 마찬가지로 오름차순 구간에 target이 존재하는지 아닌지를 판별하여 + # left와 right를 조절한다 + if nums[mid] < target <= nums[right]: + left = mid + 1 + else: + right = mid - 1 + + # while문을 모두 돌았음에도 return이 안되었다는 것은 + # target을 찾지 못했다는 의미이므로 문제 조건에 따라 -1 리턴 + return -1