Skip to content

[seungriyou] Week 11 Solutions #1571

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

Merged
merged 5 commits into from
Jun 14, 2025
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
61 changes: 61 additions & 0 deletions binary-tree-maximum-path-sum/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# https://leetcode.com/problems/binary-tree-maximum-path-sum/

from typing import Optional

# Definition for a binary tree node.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right

class Solution:
def maxPathSum(self, root: Optional[TreeNode]) -> int:
"""
[Complexity]
- TC: O(n) (각 node를 한 번씩 방문)
- SC: O(height) (tree의 높이만큼 call stack)

[Approach]
path에는 같은 node가 두 번 들어갈 수 없으므로, 각 node 마다 해당 node를 지난 path의 max sum은 다음과 같이 구할 수 있다.
= (1) left child를 지나는 path의 max_sum
+ (2) right child를 지나는 path의 max_sum
+ (3) 현재 node의 val
따라서 이는 재귀 문제로 풀 수 있으며, 각 단계마다 global max sum과 비교하며 값을 업데이트 하면 된다.

이때 node의 값은 음수가 될 수 있으므로, (1) & (2)가 음수라면 0으로 처리해야 한다. 즉,
= (1) max(get_max_sum(node.left), 0)
+ (2) max(get_max_sum(node.right), 0)
+ (3) 현재 node의 val
과 같이 구하고, global max sum과 이 값을 비교하면 된다.

또한, 현재 보고 있는 node가 get_max_sum() 함수의 반환값은
= 현재 node의 값 + (1) & (2) 중 큰 값
이어야 한다. 왜냐하면 현재 node를 두 번 이상 방문할 수 없기 때문이다.
"""

res = -1001

def get_max_sum(node):
"""주어진 node가 root인 tree에서, root를 지나는 path의 max sum을 구하는 함수"""
nonlocal res

# base condition
if not node:
return 0

# compute left & right child's max path sum
left_max_sum = max(get_max_sum(node.left), 0)
right_max_sum = max(get_max_sum(node.right), 0)

# compute max path sum at this step
max_sum = left_max_sum + right_max_sum + node.val

# update res
res = max(res, max_sum)

return node.val + max(left_max_sum, right_max_sum)

get_max_sum(root)

return res
130 changes: 130 additions & 0 deletions graph-valid-tree/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# https://leetcode.com/problems/graph-valid-tree/

from typing import List

class Solution:
def validTree_bfs(self, n: int, edges: List[List[int]]) -> bool:
"""
[Complexity] (첫 번째 조건을 통과하면 e = n - 1일 것이므로, n으로 변환도 가능)
- TC: O(v + e) (== O(n + len(edges)))
- SC: O(v + e) (graph)

[Approach]
valid tree란 다음의 두 조건을 만족하는 undirected graph이다.
1) acyclic -> edges의 개수가 n - 1개인지 확인 (-> early stop 가능)
2) connected -> 모든 node가 모두 방문되었는지, 즉 len(visited) == n인지 확인

이러한 조건을 BFS로 확인할 수 있다.
"""
from collections import deque

# edge의 개수가 n - 1이 아니라면 빠르게 반환 (-> acyclic & connected 여부 확인)
if len(edges) != n - 1:
return False

# undirected graph 구성
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)

# 0번 노드부터 시작
visited = {0}
q = deque([0])

# BFS
while q:
pos = q.popleft()
for npos in graph[pos]:
if npos not in visited:
visited.add(npos)
q.append(npos)

# visited에 모든 노드가 들어가있다면 true (-> connected 여부 확인)
return len(visited) == n

def validTree_dfs(self, n: int, edges: List[List[int]]) -> bool:
"""
[Complexity]
- TC: O(v + e) (== O(n + len(edges)))
- SC: O(v + e) (graph) (call stack의 경우 O(v))

[Approach]
valid tree의 조건을 DFS로 확인할 수 있다.
"""
# edge의 개수가 n - 1이 아니라면 빠르게 반환 (-> acyclic & connected 여부 확인)
if len(edges) != n - 1:
return False

# undirected graph 구성
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)

visited = set()

def dfs(pos):
# base condition
if pos in visited:
return

# visit 처리
visited.add(pos)

# recur
for npos in graph[pos]:
dfs(npos)

dfs(0)

# visited에 모든 노드가 들어가있다면 true (-> connected 여부 확인)
return len(visited) == n

def validTree(self, n: int, edges: List[List[int]]) -> bool:
"""
[Complexity]
- TC: O(e * α(v))
- e 만큼 반복
- 각 union-find는 path compression으로 인해 α(v)이며, 이는 거의 상수
=> e = n - 1이므로 O(n)으로도 표현 가능
- SC: O(n)

[Approach]
union-find로 undirected graph의 cycle 여부를 판단할 수 있다.
따라서 valid tree의 두 조건 중 connected 조건을 확인할 때 union-find를 사용할 수 있다.
1) acyclic -> edges의 개수가 n - 1개인지 확인 (-> early stop 가능)
2) connected -> union-find 시 parent가 같은 경우가 있는지 확인
"""
# edge의 개수가 n - 1이 아니라면 빠르게 반환 (-> acyclic & connected 여부 확인)
if len(edges) != n - 1:
return False

# union-find functions
def find_parent(x):
if parent[x] != x:
parent[x] = find_parent(parent[x])
return parent[x]

def union_parent(x, y):
px, py = find_parent(x), find_parent(y)

# cyclic 하다면(= x와 y의 parent가 같다면) False 반환
if px == py:
return False

# union
if px < py:
parent[py] = px
else:
parent[px] = py

return True

# perform union-find
parent = [i for i in range(n)]
for x, y in edges:
if not union_parent(x, y):
return False

return True
30 changes: 30 additions & 0 deletions merge-intervals/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# https://leetcode.com/problems/merge-intervals/

from typing import List

class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
"""
[Complexity]
- TC: O(nlogn) (sorting)
- SC: O(1) (res 제외)

[Approach]
intervals를 오름차순 정렬하면, 이를 순회하면서 끝 값만 비교하며 interval이 서로 겹치는 경우와 겹치지 않는 경우를 판단할 수 있다.
이전 interval과 겹치는 경우라면, 이전 끝 값과 현재 끝 값 중 큰 값으로 업데이트하면 된다.
"""
res = []

# interval의 시작 값이 작은 순으로 정렬
# -> intervals를 순회하며 끝 값만 비교하며 non-overlapping/overlapping interval 판단 가능
intervals.sort()

for s, e in intervals:
# (1) non-overlapping: res가 비었거나, 이전 e < 현재 s인 경우 -> 그대로 추가
if not res or res[-1][1] < s:
res.append([s, e])
# (2) overlapping: 이전 e >= 현재 s인 경우 -> 이전 e와 현재 e 중 큰 값으로 업데이트
else:
res[-1][1] = max(res[-1][1], e)

return res
42 changes: 42 additions & 0 deletions missing-number/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# https://leetcode.com/problems/missing-number/

from typing import List

class Solution:
def missingNumber_math(self, nums: List[int]) -> int:
"""
[Complexity]
- TC: O(n) (sum(nums))
- SC: O(1)

[Approach]
수학적으로 생각해보면, missing number는 (0 ~ n까지 값의 합) - (nums의 합)이 된다.
"""
# (sum of [0, n]) - (sum(nums))
n = len(nums)
return n * (n + 1) // 2 - sum(nums)

def missingNumber(self, nums: List[int]) -> int:
"""
[Complexity]
- TC: O(n)
- SC: O(1)

[Approach]
bit manipulation 관점으로 접근해보면, missing number를 제외한 나머지 값은 다음 두 가지 경우에서 모두 발견된다.
(1) 0 ~ n까지의 값
(2) nums의 원소
missing number는 이 두 가지 케이스 중 (1)에서만 한 번 발견되므로, 이를 XOR 연산으로 검출해 낼 수 있다.
(짝수 번 등장한 값은 사라짐)
"""

res = 0

# (1) 0 ~ n까지의 값 XOR
for i in range(len(nums) + 1):
res ^= i
# (2) nums의 원소 XOR
for n in nums:
res ^= n

return res
42 changes: 42 additions & 0 deletions reorder-list/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# https://leetcode.com/problems/reorder-list/

from typing import Optional

# Definition for singly-linked list.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next

class Solution:
def reorderList(self, head: Optional[ListNode]) -> None:
"""
Do not return anything, modify head in-place instead.
"""
"""
[Complexity]
- TC: O(n)
- SC: O(1)

[Approach]
1. linked-list의 중간 지점을 찾은 후, left / right list를 나눈다.
2. right list를 reverse 한다.
3. left와 right에서 노드를 하나씩 가져와 연결한다.
"""

# linked-list의 중간 지점을 찾은 후, left / right list 나누기
slow = fast = head
while fast and fast.next:
slow = slow.next # -> fast가 linked-list의 끝에 도달하면, slow는 중앙에 위치
fast = fast.next.next

# (slow부터 시작하는) right list를 reverse (w. 다중 할당)
prev, curr = None, slow
while curr:
curr.next, prev, curr = prev, curr, curr.next

# left와 right list에서 노드를 각각 하나씩 가져와 연결 (w. 다중 할당)
left, right = head, prev
while right.next:
left.next, left = right, left.next
right.next, right = left, right.next