-
-
Notifications
You must be signed in to change notification settings - Fork 336
[liza0525] WEEK 13 Solutions #2618
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
295d757
4ad49a4
3bdefd3
8d50e73
15aa61d
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,62 @@ | ||
| import heapq | ||
|
|
||
| # 7기 풀이 | ||
| class MedianFinder: | ||
| # 공간 복잡도: O(n) | ||
| # - 각각의 힙은 추가되는 수의 개수의 공간을 사용 | ||
| def __init__(self): | ||
| self.pre_list = [] # 앞쪽 구간, 최대 힙으로 | ||
| self.post_list = [] # 뒤쪽 구간, 최소 힙으로 | ||
|
|
||
| # 시간 복잡도: O(log n) | ||
| # - 힙을 사용하여 앞쪽 구간 및 뒤쪽 구간에 새로운 원소를 넣기 때문에, 메소드 호출 때마다 각 배열의 크기에 시간 복잡도가 정해진다. | ||
| # 공간 복잡도: O(1) | ||
| # - 해당 메소드에서는 클래스 멤버 변수 사용하는 것 이외에는 몇 개의 변수만 이용 | ||
|
Comment on lines
+11
to
+14
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.
|
||
| def addNum(self, num: int) -> None: | ||
| if not self.pre_list or num < -self.pre_list[0]: | ||
| # 앞쪽 구간에 값을 넣을 경우 | ||
| # num이 pre_list의 최댓값보다 작을 때 pre_list에 heappush를 한다. | ||
| # 최대힙 구현을 하기 위해 마이너스 값으로 넣는다. | ||
| heapq.heappush(self.pre_list, -num) | ||
| else: | ||
| # 뒤쪽 구간에 값을 넣을 경우 | ||
| # num이 post_list의 최솟값보다 클 때 post_list에 heappush를 한다. | ||
| heapq.heappush(self.post_list, num) | ||
|
|
||
| pre_len, post_len = len(self.pre_list), len(self.post_list) | ||
|
|
||
| # 힙의 크기 차이가 2 이상 날 때는 중간값 조정을 해주기 위해 요소를 다른 쪽으로 옮겨준다. | ||
|
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. 두 개의 힙을 활용하고, rebalance 하는 로직이 깔끔해서 읽기 좋았습니다! |
||
| if pre_len == post_len + 2: | ||
| # pre_list에 요소가 몰린 경우이므로, | ||
| # pre_list의 최댓값을 pop한 후 post_list의 최솟값으로 push한다. | ||
| heapq.heappush( | ||
| self.post_list, | ||
| -heapq.heappop(self.pre_list) | ||
| ) | ||
| elif post_len == pre_len + 2: | ||
| # post_list에 요소가 몰린 경우이므로, | ||
| # post_list의 최솟값을 pop한 후 pre_list의 최댓값으로 push한다. | ||
| heapq.heappush( | ||
| self.pre_list, | ||
| -heapq.heappop(self.post_list) | ||
| ) | ||
|
|
||
| # 시간 복잡도: O(1) | ||
| # - 각 힙에서의 최솟값 또는 최대값에 접근하는 정도 + 간단한 중간값 계산 | ||
| # 공간 복잡도: O(1) | ||
| # - 해당 메소드에서는 클래스 멤버 변수 사용하는 것 이외에는 몇 개의 변수만 이용 | ||
| def findMedian(self) -> float: | ||
| pre_len, post_len = len(self.pre_list), len(self.post_list) | ||
|
|
||
| if pre_len > post_len: | ||
| # pre_list의 길이가 더 큰 경우에는 중간값이 pre_list에 있으므로 | ||
| # pre_list의 최댓값을 return | ||
| return -self.pre_list[0] | ||
| elif pre_len < post_len: | ||
| # post_list의 길이가 더 큰 경우에는 중간값이 post_list에 있으므로 | ||
| # post_list의 최솟값을 return | ||
| return self.post_list[0] | ||
| else: | ||
| # 두 힙의 길이가 같은 경우에는 | ||
| # pre_list의 최댓값과 post_list의 최솟값의 평균값을 return | ||
| return (-self.pre_list[0] + self.post_list[0]) / 2 | ||
|
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)이다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # 7기 풀이 | ||
| # 시간 복잡도: O(n) | ||
| # - intervals의 모든 요소를 접근하므로 intervals의 길이(n)만큼 시간 소요 | ||
| # 공간 복잡도: O(1) | ||
| # - 결과 변수인 res를 제외하면, 몇 가지 변수 이외에 사용하지 않음 | ||
| class Solution: | ||
| def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]: | ||
| res = [newInterval] # newInterval을 맨 처음에 넣어줌 | ||
|
|
||
| for curr_start, curr_end in intervals: # intervals를 돌며 현재의 구간을 curr_start, curr_end로 지정 | ||
| prev_start, prev_end = res[-1] # 이전 구간은 res의 마지막 요소인 prev_start, prev_end로 지정 | ||
| if curr_start <= prev_end: | ||
| # 1. curr_start가 prev_end와 작거나 같다는 것은 | ||
| # 다음의 두 케이스로 나뉘어진다.(자세한 설명은 각 조건 내에 상술) | ||
| if curr_end < prev_start: | ||
| # 1-1. curr 구간이 prev 구간보다 아예 앞서는 경우 | ||
| # 이 경우에는 구간의 순서를 변경해줘야 하므로 | ||
| # res[-1]을 curr 요소로 변경해준 후 | ||
| # prev 구간을 다시 append해준다. | ||
| res[-1][0], res[-1][1] = curr_start, curr_end | ||
| res.append([prev_start, prev_end]) | ||
| else: | ||
| # 1-2. curr 구간과 prev 구간이 겹치는 경우 | ||
| # 이 경우에는 start끼리 비교하여 작은 수를 res[-1]의 start로 | ||
| # end 끼리 비교하여 큰 수를 res[-1]의 end 값으로 재할당 | ||
| res[-1][0] = min(curr_start, prev_start) | ||
| res[-1][1] = max(curr_end, prev_end) | ||
| else: | ||
| # 2. curr_start가 prev_end보다 큰 경우에는 | ||
| # 구간의 순서를 바꾸거나 겹치는 구간을 merge할 필요가 없으므로 | ||
| # res에 현재 구간을 append해준다. | ||
| res.append([curr_start, curr_end]) | ||
|
|
||
| return 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. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
풀이 1:
|
| 유저 분석 | 실제 분석 | 결과 | |
|---|---|---|---|
| Time | O(n) | O(n) | ✅ |
| Space | O(h) | O(h) | ✅ |
피드백: 중위 순회는 모든 노드를 방문하므로 시간 복잡도는 O(n), 재귀 호출로 인해 최대 트리 높이만큼 스택 공간이 사용된다.
개선 제안: 현재 구현이 적절해 보입니다.
풀이 2: Solution.kthSmallest (값 저장 리스트) — Time: ✅ O(n) → O(n) / Space: ✅ O(n) → O(n)
| 유저 분석 | 실제 분석 | 결과 | |
|---|---|---|---|
| Time | O(n) | O(n) | ✅ |
| Space | O(n) | O(n) | ✅ |
피드백: 모든 노드를 탐색하여 리스트에 저장하므로 시간과 공간 모두 O(n)이다. 이 방법은 간단하지만 공간 효율이 낮다.
개선 제안: 현재 구현이 적절해 보입니다.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # 7기 풀이 | ||
| # 풀이 1, 2 모두 중위 순회를 하면 BST를 오름차순으로 순회할 수 있다는 특징을 이용해 풀이 | ||
| class Solution: | ||
| # 풀이 1 | ||
| # 시간 복잡도: O(n) | ||
| # - 최악의 경우에는 모든 노드를 탐색하는 경우이므로, 노드의 개수(n)만큼의 시간 소요 | ||
| # 공간 복잡도: O(h) | ||
| # - 재귀 스택에 트리의 깊이(h) 만큼 사용됨 | ||
| def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: | ||
| res = -1 # 결과를 저장할 변수 | ||
|
|
||
| def inorder(node): | ||
| # k와 res는 inorder 함수 외부의 변수들이므로, | ||
| # nonlocal 선언하여 접근할 수 있게 변경 | ||
| nonlocal k | ||
| nonlocal res | ||
|
|
||
| if not node: | ||
| # 노드가 None인 경우는 재귀 탐색을 끝내고 return | ||
| return | ||
|
|
||
| inorder(node.left) # 왼쪽 자식 노드를 재귀적으로 먼저 탐색 | ||
| k -= 1 # 현재 노드 탐색에서 k를 하나 줄임 | ||
| if k == 0: # k가 0이 되는 순간이 전체 노드 중에 k번째 값이 됨 | ||
| res = node.val # res에 node의 값을 할당 | ||
| return # 더이상 탐색할 필요가 없으므로 return | ||
| inorder(node.right) # 오른쪽 자식 노드를 재귀적으로 나중에 탐색 | ||
|
|
||
| inorder(root) | ||
| return res | ||
|
|
||
| # 풀이 2 | ||
| # 시간 복잡도: O(n) | ||
| # - 모든 노드를 탐색하기 때문에 노드의 개수(n)만큼의 시간 소요 | ||
| # 공간 복잡도: O(n) | ||
| # - 모든 노드의 값을 res_list에 저장하기 때문에 노드의 개수(n)만큼의 공간 차지 | ||
| def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: | ||
| res_list = [] | ||
|
|
||
| def inorder(node): | ||
| if not node: | ||
| # 노드가 없을 땐 return | ||
| return | ||
|
|
||
| inorder(node.left) # 왼쪽 자식 노드를 재귀적으로 먼저 탐색 | ||
| res_list.append(node.val) # 현재 노드를 res_list에 삽입 | ||
| inorder(node.right) # 오른쪽 자식 노드를 재귀적으로 나중에 탐색 | ||
|
|
||
|
|
||
| inorder(root) # 중위 탐색 시작 | ||
| return res_list[k - 1] # 탐색한 결과 중 k번째 요소 return |
|
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. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: BST의 정렬 특성을 활용하여, p와 q의 값에 따라 왼쪽 또는 오른쪽으로 재귀 탐색하며, 최악의 경우 트리 높이만큼 재귀 호출이 쌓인다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # 7기 풀이 | ||
| # 시간 복잡도: O(n) | ||
| # - balanced tree라면 O(log n)이 되지만, 편향 트리가 worst case이므로 | ||
| # - 이때는 모든 노드 탐색, 노드 개수(n)에 따라 시간 복잡도가 결정된다. | ||
| # - balanced tree의 경우: 매 노드 탐색 때마다 한 쪽 서브 트리를 선택해 탐색하므로 노드 개수(n)의 log 값만큼의 시간 소요 | ||
| # 공간 복잡도: O(h) | ||
| # - tree의 높이인 h만큼의 재귀 스택이 쌓임 | ||
| class Solution: | ||
| def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': | ||
| node1 = p if p.val < q.val else q # node1은 val이 작은 노드를 가르키게 설정 | ||
| node2 = q if p.val < q.val else p # node2은 val이 큰 노드를 가르키게 설정 | ||
|
|
||
| def find_lca(node): | ||
| # BST 특징에 의해 | ||
| # 자기 자신 값은 왼쪽 서브트리 노드의 값들보다 무조건 크고 | ||
| # 오른쪽 서브트리 노드들의 값보다 무조건 작다. | ||
| # 따라서 node1.val <= node.val <= node2.val이면 | ||
| # p와 q가 현재 노드를 기준으로 양쪽에 갈라지는 순간으로 판단, | ||
| # 그 노드가 LCA가 된다. | ||
|
Comment on lines
+14
to
+19
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. 논리 흐름 주석으로 차근차근 적어주신 것 같이 보면서 저도 학습하기 좋았어요~! |
||
| if node1.val <= node.val <= node2.val: | ||
| # 사이에 들어온 경우는 node 그 자체를 return | ||
| # 등호도 같이 써준다. | ||
| return node | ||
| elif node.val > node1.val and node.val > node2.val: | ||
| # node의 값이 node1의 값과 node2의 값보다 큰 경우, | ||
| # LCA는 현재 노드의 left subtree에 존재한다는 의미이므로 | ||
| # 다음 탐색은 왼쪽 자식 노드 쪽으로 재귀 탐색 | ||
| return find_lca(node.left) | ||
| elif node.val < node1.val and node.val < node2.val: | ||
| # node의 값이 node1의 값과 node2의 값보다 작은 경우, | ||
| # LCA는 현재 노드의 right subtree에 존재한다는 의미이므로 | ||
| # 다음 탐색은 오른쪽 자식 노드 쪽으로 재귀 탐색 | ||
| return find_lca(node.right) | ||
|
|
||
| # 세 케이스가 exhaustive하므로 else 없음 | ||
|
|
||
| return find_lca(root) | ||
|
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,27 @@ | ||
| from typing import List | ||
|
|
||
|
|
||
| # class Interval(object): | ||
| # def __init__(self, start, end): | ||
| # self.start = start | ||
| # self.end = end | ||
|
|
||
| # 7기 풀이 | ||
| # 시간 복잡도: O(n log n) | ||
| # - python sorting을 이용하여 정렬하기 때문에 | ||
| # 공간 복잡도: O(1) | ||
| # - 메서드 파라미터 이외에 사용하는 변수 없음 | ||
| class Solution: | ||
| def can_attend_meetings(self, intervals: List[Interval]) -> bool: | ||
| # intervals를 정렬, 이때 key는 각 요소의 start 시간을 기준으로 오름차순 해준다. | ||
| intervals.sort(key=lambda x: x.start) | ||
|
|
||
| for i in range(1, len(intervals)): | ||
| # 현재 interval 시작 시간이 이전 interval 종료 시간보다 작으면 | ||
| # 두 구간은 겹치는 구간이므로, 문제 요건에 따라 False를 early return | ||
| if intervals[i].start < intervals[i - 1].end: | ||
| return False | ||
|
|
||
| # 모든 interval 구간 확인 후 겹치지 않으며 | ||
| # True를 return | ||
| return True |
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(log n)으로, 전체 연산이 효율적이다.
개선 제안: 현재 구현이 적절해 보입니다.