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
35 changes: 35 additions & 0 deletions binary-tree-maximum-path-sum/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(h) O(h)

피드백: 전체 노드를 한 번씩 방문하는 후위 탐색을 수행하며, 재귀 호출 스택은 트리 높이만큼 쌓입니다. 노드 방문은 한 번이므로 시간 복잡도는 O(n), 재귀 스택은 최악의 경우(편향 트리) O(n), 평균적으로 O(log n).

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

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
51 changes: 51 additions & 0 deletions graph-valid-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 재귀 탐색으로 검사하여 트리 여부를 판단합니다. 방문 체크와 재귀 호출을 통해 깊이 우선 탐색을 수행하는 구조입니다.

📊 시간/공간 복잡도 분석

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

피드백: 모든 노드와 엣지를 탐색하며, 방문 여부를 체크하는 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
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.

주석을 단계별로 잘 써주셔서, 이해하기 좋았어요~


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
31 changes: 31 additions & 0 deletions merge-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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Two Pointers, Sorting
  • 설명: 이 코드는 정렬 후 두 포인터를 이용해 겹치는 구간을 병합하는 방식으로 문제를 해결합니다. 정렬과 두 포인터를 활용한 범위 병합이 핵심 패턴입니다.

📊 시간/공간 복잡도 분석

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

피드백: 정렬에 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
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.

정렬 후 두 포인터로 겹치는 구간 확장하는 구조가 직관적이라 이해하기 좋았어요!

17 changes: 17 additions & 0 deletions missing-number/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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Math
  • 설명: 이 코드는 수학적 공식을 이용하여 누락된 숫자를 찾는 방식으로, 수학적 계산을 활용하는 패턴입니다.

📊 시간/공간 복잡도 분석

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

피드백: 전체 합 계산과 리스트 합산으로 각각 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
43 changes: 43 additions & 0 deletions reorder-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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Fast & Slow Pointers, Reverse Linked List, Linked List Manipulation
  • 설명: 이 코드는 fast & slow 포인터를 사용해 리스트의 중간을 찾고, 뒤쪽 절반을 뒤집은 후 노드를 교차 연결하는 방식으로 리스트를 재구성합니다. 이 과정은 링크드 리스트 조작과 관련된 패턴을 포함합니다.

📊 시간/공간 복잡도 분석

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

피드백: 중간 노드 찾기, 리스트 뒤집기, 병합 모두 리스트를 한 번씩 순회하므로 시간 복잡도는 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
23 changes: 23 additions & 0 deletions top-k-frequent-elements/liza0525.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from collections import defaultdict
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.

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]
Loading