-
-
Notifications
You must be signed in to change notification settings - Fork 335
[liza0525] WEEK 11 Solutions #2603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9aa3b02
d97ebe7
c5da7e7
0f17d56
9ad0665
ba87002
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # 7기 풀이 | ||
| # 시간 복잡도: O(n) | ||
| # - 트리 전체를 탐색이므로 전체 노드 개수(n) 만큼 시간 소요 | ||
| # 공간 복잡도: O(h) | ||
| # - 트리의 높이(h)만큼 재귀 스택 쌓임 | ||
| # - 최악의 경우(편향 트리) O(n), 평균적으로 O(log n) | ||
| class Solution: | ||
| def maxPathSum(self, root: Optional[TreeNode]) -> int: | ||
| self.res = root.val # 결과 변수를 클래스 멤버 변수로 지정 | ||
|
|
||
| # 후위 탐색을 이용해서 문제를 풀면 된다 | ||
| def postorder(node): | ||
| if not node: # 노드가 None인 경우는 0을 리턴해준다 | ||
| return 0 | ||
|
|
||
| # 자식 노드들 각각의 최대합을 먼저 계산한다. | ||
| # 자식들의 최대합이 음수인 경우는 현재 노드의 최대합 계산에 방해가 되므로 | ||
| # 0과 비교하여 더 큰 값을 저장하도록 한다 | ||
| left_total = max(postorder(node.left), 0) | ||
| right_total = max(postorder(node.right), 0) | ||
|
|
||
| # 지금까지의 최대합(self.res)와 자식 노드 및 현재 노드 val의 합을 비교하여 | ||
| # 더 큰 값을 지금까지의 최대합으로 업데이트 한다. | ||
| # - 현재 노드가 root인 subtree의 값이 최대일 수 있기 때문에 | ||
| self.res = max( | ||
| self.res, | ||
| left_total + right_total + node.val | ||
| ) | ||
|
|
||
| # 위로 올릴 때는 왼쪽 노드와 오른쪽 노드 최대합 중에 | ||
| # 더 큰 값을 현재 노드 value와 더해서 올려준다. | ||
| return max(left_total, right_total) + node.val | ||
|
|
||
| postorder(root) | ||
| return self.res |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 모든 노드와 엣지를 탐색하며, 방문 여부를 체크하는 DFS를 수행합니다. 노드와 엣지 개수에 비례하는 시간과 공간이 소요됩니다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| from collections import defaultdict | ||
|
|
||
| # 7기 풀이 | ||
| # 시간 복잡도: O(V + E) | ||
| # - 노드 개수(V) 및 엣지의 개수(E)만큼 탐색하므로 이에 따라 시간이 걸림 | ||
| # 공간 복잡도: O(V + E) | ||
| # - visited 노드의 개수(V)만큼, node_map는 엣지의 개수(E)만큼 생성함 | ||
| # - 재귀 스택은 최대 V만큼 생김 | ||
| class Solution: | ||
| def validTree(self, n: int, edges: List[List[int]]) -> bool: | ||
| visited = [0 for _ in range(n)] | ||
| node_map = defaultdict(list) # 노드 연결 정보 저장할 mapper | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 주석을 단계별로 잘 써주셔서, 이해하기 좋았어요~ |
||
|
|
||
| for node1, node2 in edges: | ||
| # 각 엣지는 방향이 없으므로 양방향 정보 저장 | ||
| node_map[node1].append(node2) | ||
| node_map[node2].append(node1) | ||
|
|
||
| def check_is_not_cycle(prev_node, node): | ||
| if visited[node]: | ||
| # node를 이미 방문했다면 cycle이 있다는 의미이므로 tree가 아님 | ||
| # False를 return한다 | ||
| return False | ||
|
|
||
| visited[node] = 1 # 현재 노드 방문 확인 | ||
|
|
||
| next_nodes = node_map[node] | ||
| if not next_nodes: | ||
| # 더이상 방문할 것이 없다는 의미로 | ||
| # 해당 탐색은 cycle 없이 끝났다는 것을 의미 | ||
| return True | ||
|
|
||
| for next_node in next_nodes: | ||
| if prev_node == next_node: | ||
| # 양방향 노드 특성 상 prev_node 노드가 다음 노드와 같을 수 있다 | ||
| # 이 때는 체크하지 않고 넘어간다 | ||
| continue | ||
|
|
||
| if not check_is_not_cycle(node, next_node): | ||
| # 재귀 함수의 결과가 False이면 False로 전파 | ||
| return False | ||
|
|
||
| # 함수 내의 모든 로직의 실행이 끝났다면 싸이클이 없다는 의미이므로 | ||
| # True를 return | ||
| return True | ||
|
|
||
| is_not_cycle = check_is_not_cycle(None, 0) | ||
|
|
||
| # 싸이클이 없더라도 연결이 되지 않은 노드는 방문을 하지 않을 수 있기 때문에 | ||
| # visited에 0이 있는지 같이 확인하여 valid tree 여부를 return | ||
| return 0 not in visited if is_not_cycle else False | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 정렬에 O(n log n), 병합 과정은 한 번의 순회로 O(n)입니다. 정렬이 가장 큰 비용입니다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # 7기 풀이 | ||
| # 시간 복잡도: O(n log n) | ||
| # - merge하는 로직은 intervals의 길이(n)만큼의 시간 소요 | ||
| # - intervals를 sorting하는 로직은 n log n 만큼의 시간 소요 | ||
| # 공간 복잡도: O(1) | ||
| # - 결과 리스트(res) 제외, 몇 개의 변수만 사용 | ||
| class Solution: | ||
| def merge(self, intervals: List[List[int]]) -> List[List[int]]: | ||
| # 먼저 intervals를 오름차순으로 sorting한다. | ||
| # 각 요소의 첫번째 값을 기준으로 | ||
| intervals.sort() | ||
|
|
||
| i, j = 0, 1 | ||
| res = [] | ||
|
|
||
| # intervals를 탐색하는 i가 그 길이보다 작을 때 loop를 돈다 | ||
| while i < len(intervals): | ||
| curr_start, curr_end = intervals[i] # 첫번째 머지 대상을 기준으로 | ||
| while j < len(intervals): # j번째 요소들을 계속 머지함(범위 머지가 가능한 한) | ||
| next_start, next_end = intervals[j] | ||
| if curr_start <= next_start <= curr_end: # 다음 요소의 첫번째 값이 현재 요소 범위 내에 있을 때 | ||
| curr_end = max(curr_end, next_end) # 더 큰 범위로 머지 | ||
| else: # 그렇지 않으면 이번 텀에서의 머지가 완료된 것이므로 loop 탈출 | ||
| break | ||
| j += 1 # 머지가 가능한 한 j를 계속 올려준다. | ||
|
|
||
| res.append([curr_start, curr_end]) # 머지 완료된 구간은 res에 담고 | ||
| i = j # 이번 loop에서 사용했던 j가 다음 루프의 i가 되고 | ||
| j = i + 1 # j는 i의 다음 인덱스부터 다시 머지를 할 수 있도록 변경 | ||
|
|
||
| return res | ||
|
Comment on lines
+13
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 정렬 후 두 포인터로 겹치는 구간 확장하는 구조가 직관적이라 이해하기 좋았어요! |
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 전체 합 계산과 리스트 합산으로 각각 O(n), 전체적으로 O(n). 공간은 상수입니다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # 7기 풀이 | ||
| # 시간 복잡도: O(n) | ||
| # - nums의 전체 합을 계산할 때 nums의 길이 만큼 시간 소요 | ||
| # 공간 복잡도: O(1) | ||
| # - 계산에 필요한 변수 몇 개만 사용 | ||
| class Solution: | ||
| def missingNumber(self, nums: List[int]) -> int: | ||
| # nums에서 빠진 값까지 함께 더했을 때의 값 | ||
| # 최소값(0) + 최대값(len(nums))을 2로 나눈 후, nums의 개수 + 1(0 포함)의 개수만큼 곱한 값 | ||
| # 등차수열의 전체 합 | ||
| all_sum_nums = int(len(nums) / 2 * (len(nums) + 1)) | ||
|
|
||
| # nums 리스트의 전체 합 | ||
| sum_nums = sum(nums) | ||
|
|
||
| # 두 값의 차이가 곧 누락된 값 | ||
| return all_sum_nums - sum_nums |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 중간 노드 찾기, 리스트 뒤집기, 병합 모두 리스트를 한 번씩 순회하므로 시간 복잡도는 O(n). 공간은 포인터 변수만 사용하여 O(1). 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # 7기 풀이 | ||
| # 시간 복잡도: O(n) | ||
| # - head 리스트의 길이 만큼의 시간 복잡도가 든다. | ||
| # 공간 복잡도: O(1) | ||
| # - 변수 몇 개만 사용 | ||
| class Solution: | ||
| def reorderList(self, head: Optional[ListNode]) -> None: | ||
| """ | ||
| Do not return anything, modify head in-place instead. | ||
| """ | ||
|
|
||
| # slow-fast를 이용해서 리스트의 중간 노드를 찾는다 | ||
| # fast는 두 칸, slow는 한 칸을 움직이며 fast가 끝 노드에 도달할 때까지 loop를 돌면 | ||
| # slow로 중간 노드 찾을 수 있다. | ||
| slow, fast = head, head | ||
|
|
||
| while fast and fast.next: | ||
| slow = slow.next | ||
| fast = fast.next.next | ||
|
|
||
| # 중간 노드부터 마지막 노드까지의 리스트를 reversing해준다. | ||
| # prev, curr로 노드 포인터를 잡아 리스트를 뒤집느다. | ||
| prev, curr = None, slow | ||
|
|
||
| while curr: | ||
| next_ = curr.next | ||
| curr.next, prev = prev, curr | ||
| curr = next_ | ||
|
|
||
| # 원본이었던 head와 prev(중간부터 뒤집은 리스트)를 번갈아 가며 붙여준다. | ||
| # head의 포인터는 first, prev의 포인터는 second로 지정 | ||
| first, second = head, prev | ||
|
|
||
| # second의 리스트를 다 돌 때까지 노드를 서로 연결해주면 된다. | ||
| while second.next: | ||
| first_next = first.next | ||
| second_next = second.next | ||
|
|
||
| first.next = second | ||
| second.next = first_next | ||
|
|
||
| second = second_next | ||
| first = first_next |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| from collections import defaultdict | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1주차 문제입니다만.. 언젠간 꼭 풀겠다 생각하면서 이번주차에라도 풀어봤습니다..! |
||
|
|
||
| # 7기 풀이 | ||
| # 시간 복잡도: O(n log n) | ||
| # - sorting 로직으로 인해 n log n만큼(n: nums의 길이)의 시간 소요 | ||
| # 공간 복잡도: O(n) | ||
| # - nums 내에 distinct 요소의 개수만큼 count_map으로 공간 소요 | ||
| # - 최대 값: distinct요소 개수가 nums의 길이 값(n)과 동일할 때 | ||
| class Solution: | ||
| def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||
| count_map = defaultdict(int) | ||
|
|
||
| # 각 요소(num)의 개수를 저장 | ||
| for num in nums: | ||
| count_map[num] += 1 | ||
|
|
||
| # count 기준으로 내림차순 정렬한 수 k만큼 list를 잘라준다. | ||
| return [ | ||
| num for num, _ in sorted( | ||
| [(num, count) for num, count in count_map.items()], | ||
| key=lambda x: -x[1] | ||
| ) | ||
| ][:k] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 전체 노드를 한 번씩 방문하는 후위 탐색을 수행하며, 재귀 호출 스택은 트리 높이만큼 쌓입니다. 노드 방문은 한 번이므로 시간 복잡도는 O(n), 재귀 스택은 최악의 경우(편향 트리) O(n), 평균적으로 O(log n).
개선 제안: 현재 구현이 적절해 보입니다.