Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions non-overlapping-intervals/alphaorderly.py

Copy link
Copy Markdown
Contributor

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
"""
# 시간 복잡도: 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
  • 패턴: Greedy
  • 설명: 주요 아이디어가 끝점을 기준으로 정렬한 뒤, 겹치는 경우를 최소 제거로 해결하는 그리디 전략으로 구성되어 있습니다. 먼저 끝점이 작은 인터벌을 남겨두고, 겹치면 제거 카운트를 증가시키며 끝점을 유지하거나 업데이트합니다.

📊 시간/공간 복잡도 분석

유저 분석 실제 분석 결과
Time O(n log n) O(n log n)
Space O(1) O(1)

피드백: 종료점을 오름차순으로 정렬한 뒤 한 번 순회하며 겹치는 경우를 세서 제거 수를 계산한다. 추가 공간은 상수

개선 제안: 현재 구현이 적절해 보입니다.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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
  • 패턴: Union Find
  • 설명: 주어진 코드는 서로의 연결 여부를 관리하는 유니온 파인드(Disjoint Set Union)로 그래프의 연결 요소 개수를 계산한다. 간선을 하나씩 합치며 서로 다른 집합이면 연결 요소를 감소시키는 방식으로 구현된다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n + m α(n))
Space O(n)

피드백: 초기화와 패런트/랭크를 사용해 서로 다른 집합을 합치며 연결 요소 수를 업데이트한다

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

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
28 changes: 28 additions & 0 deletions remove-nth-node-from-end-of-list/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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
  • 패턴: Two Pointers, Linked List
  • 설명: 목적 노드 제거를 위해 두 포인터를 활용하는 대표적인 패턴으로, 하나를 n칸 먼저 움직이고 이후 같이 한 칸씩 이동시키며 타겟 노드를 찾는다. 연결 리스트의 한 번의 순회로 문제를 해결한다.

📊 시간/공간 복잡도 분석

유저 분석 실제 분석 결과
Time O(n) O(n)
Space O(1) O(1)

피드백: 더미 헤드 및 두 포인터를 사용해 제거 위치를 찾아 연결을 끊는다

개선 제안: 현재 구현이 적절해 보입니다.

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
22 changes: 22 additions & 0 deletions same-tree/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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)
        )
  • 패턴: Binary Search, Depth-First Search, Tre e Pattern
  • 설명: 추가 설명: 이 코드는 두 트리의 모든 노드를 재귀적으로 비교하여 동일 여부를 확인한다. 각 노드에서 좌우 자식으로 DFS 방식으로 순회하며 조건을 검사한다. 트리 구조를 다룬다는 점에서 DFS의 특성을 띈다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n)
Space O(h)

피드백: 두 트리의 동치 여부를 단순 비교하며 좌우 서브트리까지 재귀적으로 확인한다

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

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)
)
58 changes: 58 additions & 0 deletions serialize-and-deserialize-binary-tree/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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))
  • 패턴: Depth-First Search, Binary Search, Divide and Conquer, Hash Map / Hash Set
  • 설명: 전위 순회 기반으로 트리를 방문하며 재귀적으로 노드를 생성/복원하므로 DFS 구성이며, 재귀적 탐색이 핵심이다. 문제 맥락에서 분할된 서브트리 재구성은 Divide and Conquer의 성격도 보인다.

📊 시간/공간 복잡도 분석

유저 분석 실제 분석 결과
Time O(n) O(n)
Space O(n) O(n)

피드백: 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))
Loading