Skip to content
Merged
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
20 changes: 20 additions & 0 deletions non-overlapping-intervals/liza0525.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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Greedy, Sorting
  • 설명: 이 코드는 구간을 종료 기준으로 정렬한 후, 겹치는 구간을 최소화하기 위해 탐욕적으로 선택하는 방식으로 해결합니다. 정렬과 선택 과정이 핵심이므로 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,20 @@
# 7기 풀이
# 시간 복잡도: O(n log n)
# - sorting에 O(n log n), 이후 전체 탐색에 O(n)
# - 종합 O(n log n)
# 공간 복잡도: O(1)
# - 추가 자료구조 없이 변수만 사용
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
intervals.sort(key=lambda x: x[1]) # end 기준으로 구간들을 오름차순
res = 0

prev_end = intervals[0][1] # 제일 첫번째 구간의 end를 prev_end로 저장

for curr_start, curr_end in intervals[1:]: # 1번 인덱스부터 loop
if curr_start < prev_end: # 이전 구간 end와 curr_start 구간이 겹칠 때
res += 1 # res를 하나 올린다
else: # 겹치지 않는다면
prev_end = curr_end # 지금 구간의 end를 다음 loop의 prev_end가 되도록 할당

return res
51 changes: 51 additions & 0 deletions number-of-connected-components-in-an-undirected-graph/liza0525.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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Breadth First Search
  • 설명: 이 코드는 그래프의 연결요소를 찾기 위해 BFS를 활용하여 노드들을 탐색합니다. 큐를 이용한 탐색 방식이 핵심입니다.

📊 시간/공간 복잡도 분석

유저 분석 실제 분석 결과
Time O(V + E) O(V + E)
Space O(V + E) O(V + E)

피드백: 인접 리스트를 이용한 BFS 탐색으로 모든 노드와 엣지를 한 번씩 방문하므로 시간 복잡도는 노드와 엣지의 개수에 비례합니다. 방문 여부를 저장하는 배열과 그래프 표현이 공간을 차지합니다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import List
from collections import defaultdict, deque


# 7기 풀이
# 시간 복잡도: O(V + E)
# - 모든 노드과 연결된 엣지를 탐색하면서 component 개수를 확인하므로 노드 개수(V)와 엣지 개수(E)만큼 시간 소요
# 공간 복잡도: O(V + E)
# - 노드의 개수(V)와 엣지의 개수(E)만큼 mappers를 만드므로 V + E 만큼 공간 사용
class Solution:
def count_components(self, n: int, edges: List[List[int]]) -> int:
res = 0
mappers = defaultdict(list)

for node1, node2 in edges:
# 무방향 그래프의 정보 저장
mappers[node1].append(node2)
mappers[node2].append(node1)

visited = [0 for _ in range(n)]

def bfs(node):
# bfs로 어떤 node들이 연결되어 있는 지 탐색한다.
queue = deque([node])

while queue: # queue에 요소가 있는 동안은 계속 탐색
node = queue.popleft() # queue에서 가장 빠른 요소 pop
visited[node] = 1 # 해당 노드는 방문 처리

next_nodes = mappers.get(node, []) # mappers에서 다음 노드 정보 가져오기
for next_node in next_nodes:
if visited[next_node]:
# 다음 노드가 이미 방문한 노드라면 탐색하지 않고 스킵해도 됨
continue
# 다음 노드를 queue에 저장하여 다음 탐색 때 이용
queue.append((next_node))


for node in range(n):
if visited[node]:
# 이미 방문 처리가 되었다면 해당 노드는 이전에 탐색 완료되었다는 의미
# bfs 탐색의 시작점으로 사용하지 않고 skip한다
continue

# bfs 탐색
bfs(node)

# 탐색을 마쳤다면 component 하나를 찾았다는 의미로 res를 하나 올린다.
res += 1

return res
30 changes: 30 additions & 0 deletions remove-nth-node-from-end-of-list/liza0525.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.

🏷️ 알고리즘 패턴 분석

  • 패턴: DFS
  • 설명: 이 코드는 재귀 호출을 이용하여 리스트의 끝에서부터 역순으로 노드 위치를 계산하는 DFS 패턴을 사용합니다. 부모와 자식 노드 간의 재귀 호출을 통해 노드 제거 위치를 찾습니다.

📊 시간/공간 복잡도 분석

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

피드백: 단일 순회와 재귀 호출로 뒤에서 n번째 노드를 찾으며, 재귀 스택이 노드 수만큼 쌓입니다. 시간은 노드 수에 비례합니다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 7기 풀이
# 시간 복잡도: O(n)
# - 마지막 노드까지, 모든 노드를 탐색해야 하므로 노드의 개수에 시간 복잡도가 정해짐
# 공간 복잡도: O(n)
# - 재귀 스택이 노드의 개수만큼 쌓임
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# dummy 노드와 next node 설정 및 curr_node로 포인터 지정
dummy = ListNode()
dummy.next = head
curr_node = head

def dfs(parent, node):
if not node:
# 끝에 도달했으므로 카운팅 시작점 0을 return
# 호출부에서 +1을 하면 마지막 노드가 1이 됨
return 0

curr_i = dfs(node, node.next) + 1

# (뒤로부터 셌을 때의) 현재 노드의 순번이 주어진 n과 같을 때
if curr_i == n:
# 부모 노드의 next 노드를, 현재 노드의 next 노드로 변경하여
# 현재 노드의 연결을 끊는다.
parent.next = node.next

return curr_i

dfs(dummy, curr_node)
return dummy.next
36 changes: 36 additions & 0 deletions same-tree/liza0525.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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Depth-First Search
  • 설명: 이 코드는 두 트리를 비교하기 위해 스택을 활용한 DFS 방식으로 노드를 순차 탐색하며, 재귀 대신 반복문을 사용합니다.

📊 시간/공간 복잡도 분석

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

피드백: 모든 노드를 한 번씩 방문하며, 스택에 최대 트리의 너비만큼 쌓이므로 공간 복잡도는 너비에 비례합니다. 시간은 노드 수에 비례합니다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 7기 풀이
# 시간 복잡도: O(n)
# - 두 트리의 노드 개수가 동일할 때 모든 노드를 탐색하므로 O(n)
# - 다를 경우 early return으로 그보다 적게 탐색
# 공간 복잡도: O(n)
# - stack에는 최대 트리의 너비(width)만큼 pair가 쌓임
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
stack = [(p, q)] # p와 q를 tuple의 형태로 stack에 저장

while stack: # stack의 요소가 다 없어질 때까지 트리 탐색
p_node, q_node = stack.pop() # node 꺼내기

if not p_node and not q_node:
# 둘 다 없는 경우(null인 경우)는 leaf node로부터 온 것이기 때문에
# 더이상 탐색을 안해도 됨
continue
elif (
not p_node and q_node
or p_node and not q_node
):
# 둘 중 하나만 null인 경우에는 tree가 같지 않으므로
# False로 early return
return False
elif p_node.val != q_node.val:
# 둘 다 있는데 값이 같지 않은 경우도
# False로 early return
return False

# 두 트리의 왼쪽 쌍과 오른쪽 쌍을 각각 stack에 저장
stack.append((p_node.left, q_node.left))
stack.append((p_node.right, q_node.right))

# 모든 loop를 다 돌았다면 두 트리가 같다는 의미로
# True로 return
return True
56 changes: 56 additions & 0 deletions serialize-and-deserialize-binary-tree/liza0525.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.

🏷️ 알고리즘 패턴 분석

  • 패턴: DFS
  • 설명: 이 코드는 재귀적 탐색을 통해 트리의 노드를 순회하며 직렬화와 역직렬화를 수행하므로 DFS 패턴에 속합니다.

📊 시간/공간 복잡도 분석

ℹ️ 이 파일에는 2가지 풀이가 포함되어 있어 각각 분석합니다.

풀이 1: Codec.serialize — Time: O(n) / Space: O(n)
복잡도
Time O(n)
Space O(n)

피드백: 모든 노드를 방문하여 값을 리스트에 저장하므로 시간과 공간 모두 노드 수에 비례합니다.

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

풀이 2: Codec.deserialize — Time: O(n) / Space: O(n)
복잡도
Time O(n)
Space O(n)

피드백: 모든 노드를 재귀적으로 생성하며, 문자열을 순차적으로 처리하므로 시간과 공간 모두 노드 수에 비례합니다.

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

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

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.

이 문제는 엄격한 조건이 없다보니 각자 구현을 보는 재미가 있는 것 같습니다👀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 그렇네요! 풀이 구경해보고 왔는데 BFS로 푸셨군요 :) 잘 보았습니다~!

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from collections import deque

# 7기 풀이
# 시간 복잡도: O(n)
# - 모든 노드를 순회하여 serialize / deserialize 하므로 노드의 개수만큼 시간 소요
# 공간 복잡도: O(n)
# - 모든 노드에 대한 값을 list에 넣어 사용하므로 노드의 개수만큼 공간 사용
class Codec:
DELIMITER = "$" # serialize할 때 노드의 값을 구분하기 위한 구분자
NONE_VALUE = "N" # serialize할 때 노드가 None인 경우를 구분하기 위한 구분자

def serialize(self, root):
"""Encodes a tree to a single string.

:type root: TreeNode
:rtype: str
"""
data = [] # node의 값들을 저장할 list 추가

def dfs(node):
if not node:
# node가 None인 경우에는 None 구분자를 저장
data.append(self.NONE_VALUE)
return

data.append(str(node.val)) # node가 있는 경우에 val를 data에 저장

dfs(node.left) # 왼쪽 자식 노드 탐색
dfs(node.right) # 오른쪽 자식 노드 탐색

dfs(root) # 전체 트리 탐색
return f"{self.DELIMITER}".join(data) # 탐색하여 저장한 data list를 구분자를 이용해 string으로 변환하여 return

def deserialize(self, data):
"""Decodes your encoded data to tree.

:type data: str
:rtype: TreeNode
"""

data = deque(data.split(self.DELIMITER)) # string 값을 구분자를 기준으로 split 한 후 deque에 저장

def dfs():
val = data.popleft() # data 값 중 가장 왼쪽에 있는 값을 pop
if val == self.NONE_VALUE:
# None 구분자의 값이 나온 경우에는 node가 없었던 것이므로 None 리턴
return None

node = TreeNode(val) # 노드 생성
node.left = dfs() # 왼쪽 자식 노드 생성
node.right = dfs() # 오른쪽 자식 노드 생성

return node # 완성한 노드 return

root = dfs()
return root
Loading