-
-
Notifications
You must be signed in to change notification settings - Fork 361
[alphaorderly] WEEK 12 Solutions #2854
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
base: main
Are you sure you want to change the base?
Changes from all commits
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,32 @@ | ||
| """ | ||
| # 시간 복잡도: O(n log n) | ||
| # 공간 복잡도: O(1) | ||
| # | ||
| # 1. 인터벌을 끝점을 기준으로 오름차순 정렬한다. | ||
| # 2. 첫 번째 인터벌의 끝점을 기준으로 순회하면서, | ||
| # 다음 인터벌의 시작점이 현재 끝점보다 작으면 겹치는 것으로 간주하여 카운트를 증가시킨다. | ||
| # - 겹치면(count += 1) 끝점은 그대로 둔다. | ||
| # - 겹치지 않으면 끝점을 현재 인터벌의 끝점으로 업데이트한다. | ||
| # 3. 마지막에 카운트를 반환한다. | ||
| # | ||
| # 왜 최선인가? | ||
| # - 끝점이 가장 작은 것부터 남겨두면 이후로 더 많은 인터벌을 남길 수 있다. | ||
| # - 이는 최대한 많은 인터벌을 남기기 위해 최소 개수의 인터벌만 제거하게 되는 그리디 전략이다. | ||
| """ | ||
| class Solution: | ||
| def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: | ||
| intervals.sort(key=lambda k: k[1]) | ||
| N = len(intervals) | ||
|
|
||
| count = 0 | ||
| end = intervals[0][1] | ||
|
|
||
| for i in range(1, N): | ||
| event_start, event_end = intervals[i] | ||
|
|
||
| if event_start < end: | ||
| count += 1 | ||
| else: | ||
| end = event_end | ||
|
|
||
| return count |
|
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. 🏷️ 알고리즘 패턴 분석number-of-connected-components-in-an-undirected-graph/alphaorderly.py"""
# 시간 복잡도: O(n) (Union Find 연산은 경로 압축과 rank 활용으로 Amortized O(1))
# 공간 복잡도: O(n)
#
# Union-Find(유니온 파인드, Disjoint Set Union) 알고리즘을 사용해 무방향 그래프의 연결 요소(connected components) 개수를 계산한다.
#
# 알고리즘 단계:
# 1. 각 노드는 자기 자신을 부모로 갖도록(parent 배열) 초기화한다.
# 2. 트리의 깊이(랭크, rank)를 저장하는 배열을 초기화한다.
# 3. 간선을 하나씩 확인하며 union 연산으로 두 노드를 같은 집합으로 합친다.
# - 이미 같은 집합인 경우엔 아무 작업도 하지 않는다.
# - 서로 다른 집합을 합치면, 연결 요소 개수를 1 감소시킨다.
# 4. 모든 간선을 처리하고 남은 연결 요소 개수(ans)를 반환한다.
"""
class UnionFind:
def __init__(self, n: int):
self.parent = [-1] * n
self.rank = [0] * n
def find(self, target: int) -> int:
if self.parent[target] == -1:
return target
self.parent[target] = self.find(self.parent[target])
return self.parent[target]
def union(self, a: int, b: int) -> bool:
a = self.find(a)
b = self.find(b)
if a == b:
return False
if self.rank[a] < self.rank[b]:
a, b = b, a
self.parent[b] = a
if self.rank[a] == self.rank[b]:
self.rank[a] += 1
return True
class Solution:
def countComponents(self, n: int, edges: List[List[int]]) -> int:
uf = UnionFind(n)
ans = n
for a, b in edges:
if uf.union(a, b):
ans -= 1
return ans
📊 시간/공간 복잡도 분석
피드백: 초기화와 패런트/랭크를 사용해 서로 다른 집합을 합치며 연결 요소 수를 업데이트한다 개선 제안: 현재 구현이 적절해 보입니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
|
|
||
| """ | ||
| # 시간 복잡도: O(n) (Union Find 연산은 경로 압축과 rank 활용으로 Amortized O(1)) | ||
| # 공간 복잡도: O(n) | ||
| # | ||
| # Union-Find(유니온 파인드, Disjoint Set Union) 알고리즘을 사용해 무방향 그래프의 연결 요소(connected components) 개수를 계산한다. | ||
| # | ||
| # 알고리즘 단계: | ||
| # 1. 각 노드는 자기 자신을 부모로 갖도록(parent 배열) 초기화한다. | ||
| # 2. 트리의 깊이(랭크, rank)를 저장하는 배열을 초기화한다. | ||
| # 3. 간선을 하나씩 확인하며 union 연산으로 두 노드를 같은 집합으로 합친다. | ||
| # - 이미 같은 집합인 경우엔 아무 작업도 하지 않는다. | ||
| # - 서로 다른 집합을 합치면, 연결 요소 개수를 1 감소시킨다. | ||
| # 4. 모든 간선을 처리하고 남은 연결 요소 개수(ans)를 반환한다. | ||
| """ | ||
| class UnionFind: | ||
| def __init__(self, n: int): | ||
| self.parent = [-1] * n | ||
| self.rank = [0] * n | ||
|
|
||
| def find(self, target: int) -> int: | ||
| if self.parent[target] == -1: | ||
| return target | ||
|
|
||
| self.parent[target] = self.find(self.parent[target]) | ||
| return self.parent[target] | ||
|
|
||
| def union(self, a: int, b: int) -> bool: | ||
| a = self.find(a) | ||
| b = self.find(b) | ||
|
|
||
| if a == b: | ||
| return False | ||
|
|
||
| if self.rank[a] < self.rank[b]: | ||
| a, b = b, a | ||
|
|
||
| self.parent[b] = a | ||
| if self.rank[a] == self.rank[b]: | ||
| self.rank[a] += 1 | ||
|
|
||
| return True | ||
|
|
||
| class Solution: | ||
| def countComponents(self, n: int, edges: List[List[int]]) -> int: | ||
| uf = UnionFind(n) | ||
| ans = n | ||
|
|
||
| for a, b in edges: | ||
| if uf.union(a, b): | ||
| ans -= 1 | ||
|
|
||
| return ans |
|
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. 🏷️ 알고리즘 패턴 분석remove-nth-node-from-end-of-list/alphaorderly.py"""
# 시간 복잡도: O(n)
# 공간 복잡도: O(1)
#
# 한 번의 순회(one pass)로 연결 리스트의 끝에서 n번째 노드를 제거하는 방법:
# 1. dummy 노드를 만들어 head의 앞에 연결한다.
# 2. forerunner 포인터를 n칸 먼저 이동시킨다.
# 3. forerunner와 lastcomer(초기: dummy)를 같이 한 칸씩 이동하며, forerunner가 끝(None)에 도달하면 lastcomer는 제거할 노드 바로 앞에 위치하게 된다.
# 4. lastcomer.next를 갱신하여 n번째 노드를 제거한다.
"""
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy = ListNode(0)
dummy.next = head
forerunner = head
lastcomer = dummy
for _ in range(n):
forerunner = forerunner.next
while forerunner:
forerunner = forerunner.next
lastcomer = lastcomer.next
lastcomer.next = lastcomer.next.next
return dummy.next
📊 시간/공간 복잡도 분석
피드백: 더미 헤드 및 두 포인터를 사용해 제거 위치를 찾아 연결을 끊는다 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| """ | ||
| # 시간 복잡도: O(n) | ||
| # 공간 복잡도: O(1) | ||
| # | ||
| # 한 번의 순회(one pass)로 연결 리스트의 끝에서 n번째 노드를 제거하는 방법: | ||
| # 1. dummy 노드를 만들어 head의 앞에 연결한다. | ||
| # 2. forerunner 포인터를 n칸 먼저 이동시킨다. | ||
| # 3. forerunner와 lastcomer(초기: dummy)를 같이 한 칸씩 이동하며, forerunner가 끝(None)에 도달하면 lastcomer는 제거할 노드 바로 앞에 위치하게 된다. | ||
| # 4. lastcomer.next를 갱신하여 n번째 노드를 제거한다. | ||
| """ | ||
| class Solution: | ||
| def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: | ||
| dummy = ListNode(0) | ||
| dummy.next = head | ||
|
|
||
| forerunner = head | ||
| lastcomer = dummy | ||
|
|
||
| for _ in range(n): | ||
| forerunner = forerunner.next | ||
|
|
||
| while forerunner: | ||
| forerunner = forerunner.next | ||
| lastcomer = lastcomer.next | ||
|
|
||
| lastcomer.next = lastcomer.next.next | ||
|
|
||
| return dummy.next |
|
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. 🏷️ 알고리즘 패턴 분석same-tree/alphaorderly.py"""
시간 복잡도 : O(n)
공간 복잡도 : O(n)
재귀적으로 두 이진 트리의 노드를 비교하면서 값이 같은지 확인한다.
"""
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
# 두 노드가 둘다 None 이면 True 를 반환한다.
if p is q:
return True
# 두 노드중 하나가 None 이면 False 를 반환한다.
if not (p and q):
return False
# 둘다 None이 아니라면 값을 비교하고 왼쪽과 오른쪽 서브트리도 재귀적으로 비교한다.
return (
p.val == q.val
and self.isSameTree(p.left, q.left)
and self.isSameTree(p.right, q.right)
)
📊 시간/공간 복잡도 분석
피드백: 두 트리의 동치 여부를 단순 비교하며 좌우 서브트리까지 재귀적으로 확인한다 개선 제안: 현재 구현이 적절해 보입니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| """ | ||
| 시간 복잡도 : O(n) | ||
| 공간 복잡도 : O(n) | ||
|
|
||
| 재귀적으로 두 이진 트리의 노드를 비교하면서 값이 같은지 확인한다. | ||
| """ | ||
| class Solution: | ||
| def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool: | ||
| # 두 노드가 둘다 None 이면 True 를 반환한다. | ||
| if p is q: | ||
| return True | ||
|
|
||
| # 두 노드중 하나가 None 이면 False 를 반환한다. | ||
| if not (p and q): | ||
| return False | ||
|
|
||
| # 둘다 None이 아니라면 값을 비교하고 왼쪽과 오른쪽 서브트리도 재귀적으로 비교한다. | ||
| return ( | ||
| p.val == q.val | ||
| and self.isSameTree(p.left, q.left) | ||
| and self.isSameTree(p.right, q.right) | ||
| ) |
|
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. 🏷️ 알고리즘 패턴 분석serialize-and-deserialize-binary-tree/alphaorderly.py# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
"""
# 시간 복잡도: O(n)
# 공간 복잡도: O(n)
#
# 이 코드는 preorder(전위 순회)를 이용해 이진 트리를 문자열로 직렬화하고,
# 다시 preorder 순서로 문자열을 역직렬화하여 트리를 복원한다.
#
# 1. serialize 함수는 preorder 순회로 트리 노드를 방문하며 값을 문자열로 저장하고,
# None 자리는 특수문자(*)로 표기한다.
# 2. deserialize 함수는 preorder 순서의 문자열을 한 항목씩 읽어가며
# *이면 None, 숫자면 그 값의 TreeNode를 재귀적으로 생성한다.
"""
class Codec:
def serialize(self, root):
ans = []
def preorder(node: Optional[TreeNode]):
if not node:
ans.append("*")
return
ans.append(str(node.val))
preorder(node.left)
preorder(node.right)
preorder(root)
return ','.join(ans)
def deserialize(self, data):
data = iter(data.split(','))
def preorder() -> TreeNode:
current = next(data)
if current == '*':
return None
node = TreeNode(int(current))
node.left = preorder()
node.right = preorder()
return node
return preorder()
# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))
📊 시간/공간 복잡도 분석
피드백: None 노드도 '*'로 표기하여 구조를 보존한다 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # Definition for a binary tree node. | ||
| # class TreeNode(object): | ||
| # def __init__(self, x): | ||
| # self.val = x | ||
| # self.left = None | ||
| # self.right = None | ||
|
|
||
| """ | ||
| # 시간 복잡도: O(n) | ||
| # 공간 복잡도: O(n) | ||
| # | ||
| # 이 코드는 preorder(전위 순회)를 이용해 이진 트리를 문자열로 직렬화하고, | ||
| # 다시 preorder 순서로 문자열을 역직렬화하여 트리를 복원한다. | ||
| # | ||
| # 1. serialize 함수는 preorder 순회로 트리 노드를 방문하며 값을 문자열로 저장하고, | ||
| # None 자리는 특수문자(*)로 표기한다. | ||
| # 2. deserialize 함수는 preorder 순서의 문자열을 한 항목씩 읽어가며 | ||
| # *이면 None, 숫자면 그 값의 TreeNode를 재귀적으로 생성한다. | ||
| """ | ||
| class Codec: | ||
|
|
||
| def serialize(self, root): | ||
| ans = [] | ||
|
|
||
| def preorder(node: Optional[TreeNode]): | ||
| if not node: | ||
| ans.append("*") | ||
| return | ||
|
|
||
| ans.append(str(node.val)) | ||
| preorder(node.left) | ||
| preorder(node.right) | ||
|
|
||
| preorder(root) | ||
| return ','.join(ans) | ||
|
|
||
| def deserialize(self, data): | ||
| data = iter(data.split(',')) | ||
|
|
||
| def preorder() -> TreeNode: | ||
| current = next(data) | ||
|
|
||
| if current == '*': | ||
| return None | ||
|
|
||
| node = TreeNode(int(current)) | ||
| node.left = preorder() | ||
| node.right = preorder() | ||
|
|
||
| return node | ||
|
|
||
| return preorder() | ||
|
|
||
|
|
||
| # Your Codec object will be instantiated and called as such: | ||
| # ser = Codec() | ||
| # deser = Codec() | ||
| # ans = deser.deserialize(ser.serialize(root)) |
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.
🏷️ 알고리즘 패턴 분석
non-overlapping-intervals/alphaorderly.py
📊 시간/공간 복잡도 분석
피드백: 종료점을 오름차순으로 정렬한 뒤 한 번 순회하며 겹치는 경우를 세서 제거 수를 계산한다. 추가 공간은 상수
개선 제안: 현재 구현이 적절해 보입니다.