Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 837e685

Browse files
authoredJun 14, 2025
Merge pull request #1571 from seungriyou/main
[seungriyou] Week 11 Solutions
2 parents f180b47 + 17758f5 commit 837e685

File tree

5 files changed

+305
-0
lines changed

5 files changed

+305
-0
lines changed
 
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# https://leetcode.com/problems/binary-tree-maximum-path-sum/
2+
3+
from typing import Optional
4+
5+
# Definition for a binary tree node.
6+
class TreeNode:
7+
def __init__(self, val=0, left=None, right=None):
8+
self.val = val
9+
self.left = left
10+
self.right = right
11+
12+
class Solution:
13+
def maxPathSum(self, root: Optional[TreeNode]) -> int:
14+
"""
15+
[Complexity]
16+
- TC: O(n) (각 node를 한 번씩 방문)
17+
- SC: O(height) (tree의 높이만큼 call stack)
18+
19+
[Approach]
20+
path에는 같은 node가 두 번 들어갈 수 없으므로, 각 node 마다 해당 node를 지난 path의 max sum은 다음과 같이 구할 수 있다.
21+
= (1) left child를 지나는 path의 max_sum
22+
+ (2) right child를 지나는 path의 max_sum
23+
+ (3) 현재 node의 val
24+
따라서 이는 재귀 문제로 풀 수 있으며, 각 단계마다 global max sum과 비교하며 값을 업데이트 하면 된다.
25+
26+
이때 node의 값은 음수가 될 수 있으므로, (1) & (2)가 음수라면 0으로 처리해야 한다. 즉,
27+
= (1) max(get_max_sum(node.left), 0)
28+
+ (2) max(get_max_sum(node.right), 0)
29+
+ (3) 현재 node의 val
30+
과 같이 구하고, global max sum과 이 값을 비교하면 된다.
31+
32+
또한, 현재 보고 있는 node가 get_max_sum() 함수의 반환값은
33+
= 현재 node의 값 + (1) & (2) 중 큰 값
34+
이어야 한다. 왜냐하면 현재 node를 두 번 이상 방문할 수 없기 때문이다.
35+
"""
36+
37+
res = -1001
38+
39+
def get_max_sum(node):
40+
"""주어진 node가 root인 tree에서, root를 지나는 path의 max sum을 구하는 함수"""
41+
nonlocal res
42+
43+
# base condition
44+
if not node:
45+
return 0
46+
47+
# compute left & right child's max path sum
48+
left_max_sum = max(get_max_sum(node.left), 0)
49+
right_max_sum = max(get_max_sum(node.right), 0)
50+
51+
# compute max path sum at this step
52+
max_sum = left_max_sum + right_max_sum + node.val
53+
54+
# update res
55+
res = max(res, max_sum)
56+
57+
return node.val + max(left_max_sum, right_max_sum)
58+
59+
get_max_sum(root)
60+
61+
return res

‎graph-valid-tree/seungriyou.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# https://leetcode.com/problems/graph-valid-tree/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def validTree_bfs(self, n: int, edges: List[List[int]]) -> bool:
7+
"""
8+
[Complexity] (첫 번째 조건을 통과하면 e = n - 1일 것이므로, n으로 변환도 가능)
9+
- TC: O(v + e) (== O(n + len(edges)))
10+
- SC: O(v + e) (graph)
11+
12+
[Approach]
13+
valid tree란 다음의 두 조건을 만족하는 undirected graph이다.
14+
1) acyclic -> edges의 개수가 n - 1개인지 확인 (-> early stop 가능)
15+
2) connected -> 모든 node가 모두 방문되었는지, 즉 len(visited) == n인지 확인
16+
17+
이러한 조건을 BFS로 확인할 수 있다.
18+
"""
19+
from collections import deque
20+
21+
# edge의 개수가 n - 1이 아니라면 빠르게 반환 (-> acyclic & connected 여부 확인)
22+
if len(edges) != n - 1:
23+
return False
24+
25+
# undirected graph 구성
26+
graph = [[] for _ in range(n)]
27+
for a, b in edges:
28+
graph[a].append(b)
29+
graph[b].append(a)
30+
31+
# 0번 노드부터 시작
32+
visited = {0}
33+
q = deque([0])
34+
35+
# BFS
36+
while q:
37+
pos = q.popleft()
38+
for npos in graph[pos]:
39+
if npos not in visited:
40+
visited.add(npos)
41+
q.append(npos)
42+
43+
# visited에 모든 노드가 들어가있다면 true (-> connected 여부 확인)
44+
return len(visited) == n
45+
46+
def validTree_dfs(self, n: int, edges: List[List[int]]) -> bool:
47+
"""
48+
[Complexity]
49+
- TC: O(v + e) (== O(n + len(edges)))
50+
- SC: O(v + e) (graph) (call stack의 경우 O(v))
51+
52+
[Approach]
53+
valid tree의 조건을 DFS로 확인할 수 있다.
54+
"""
55+
# edge의 개수가 n - 1이 아니라면 빠르게 반환 (-> acyclic & connected 여부 확인)
56+
if len(edges) != n - 1:
57+
return False
58+
59+
# undirected graph 구성
60+
graph = [[] for _ in range(n)]
61+
for a, b in edges:
62+
graph[a].append(b)
63+
graph[b].append(a)
64+
65+
visited = set()
66+
67+
def dfs(pos):
68+
# base condition
69+
if pos in visited:
70+
return
71+
72+
# visit 처리
73+
visited.add(pos)
74+
75+
# recur
76+
for npos in graph[pos]:
77+
dfs(npos)
78+
79+
dfs(0)
80+
81+
# visited에 모든 노드가 들어가있다면 true (-> connected 여부 확인)
82+
return len(visited) == n
83+
84+
def validTree(self, n: int, edges: List[List[int]]) -> bool:
85+
"""
86+
[Complexity]
87+
- TC: O(e * α(v))
88+
- e 만큼 반복
89+
- 각 union-find는 path compression으로 인해 α(v)이며, 이는 거의 상수
90+
=> e = n - 1이므로 O(n)으로도 표현 가능
91+
- SC: O(n)
92+
93+
[Approach]
94+
union-find로 undirected graph의 cycle 여부를 판단할 수 있다.
95+
따라서 valid tree의 두 조건 중 connected 조건을 확인할 때 union-find를 사용할 수 있다.
96+
1) acyclic -> edges의 개수가 n - 1개인지 확인 (-> early stop 가능)
97+
2) connected -> union-find 시 parent가 같은 경우가 있는지 확인
98+
"""
99+
# edge의 개수가 n - 1이 아니라면 빠르게 반환 (-> acyclic & connected 여부 확인)
100+
if len(edges) != n - 1:
101+
return False
102+
103+
# union-find functions
104+
def find_parent(x):
105+
if parent[x] != x:
106+
parent[x] = find_parent(parent[x])
107+
return parent[x]
108+
109+
def union_parent(x, y):
110+
px, py = find_parent(x), find_parent(y)
111+
112+
# cyclic 하다면(= x와 y의 parent가 같다면) False 반환
113+
if px == py:
114+
return False
115+
116+
# union
117+
if px < py:
118+
parent[py] = px
119+
else:
120+
parent[px] = py
121+
122+
return True
123+
124+
# perform union-find
125+
parent = [i for i in range(n)]
126+
for x, y in edges:
127+
if not union_parent(x, y):
128+
return False
129+
130+
return True

‎merge-intervals/seungriyou.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# https://leetcode.com/problems/merge-intervals/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
7+
"""
8+
[Complexity]
9+
- TC: O(nlogn) (sorting)
10+
- SC: O(1) (res 제외)
11+
12+
[Approach]
13+
intervals를 오름차순 정렬하면, 이를 순회하면서 끝 값만 비교하며 interval이 서로 겹치는 경우와 겹치지 않는 경우를 판단할 수 있다.
14+
이전 interval과 겹치는 경우라면, 이전 끝 값과 현재 끝 값 중 큰 값으로 업데이트하면 된다.
15+
"""
16+
res = []
17+
18+
# interval의 시작 값이 작은 순으로 정렬
19+
# -> intervals를 순회하며 끝 값만 비교하며 non-overlapping/overlapping interval 판단 가능
20+
intervals.sort()
21+
22+
for s, e in intervals:
23+
# (1) non-overlapping: res가 비었거나, 이전 e < 현재 s인 경우 -> 그대로 추가
24+
if not res or res[-1][1] < s:
25+
res.append([s, e])
26+
# (2) overlapping: 이전 e >= 현재 s인 경우 -> 이전 e와 현재 e 중 큰 값으로 업데이트
27+
else:
28+
res[-1][1] = max(res[-1][1], e)
29+
30+
return res

‎missing-number/seungriyou.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# https://leetcode.com/problems/missing-number/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def missingNumber_math(self, nums: List[int]) -> int:
7+
"""
8+
[Complexity]
9+
- TC: O(n) (sum(nums))
10+
- SC: O(1)
11+
12+
[Approach]
13+
수학적으로 생각해보면, missing number는 (0 ~ n까지 값의 합) - (nums의 합)이 된다.
14+
"""
15+
# (sum of [0, n]) - (sum(nums))
16+
n = len(nums)
17+
return n * (n + 1) // 2 - sum(nums)
18+
19+
def missingNumber(self, nums: List[int]) -> int:
20+
"""
21+
[Complexity]
22+
- TC: O(n)
23+
- SC: O(1)
24+
25+
[Approach]
26+
bit manipulation 관점으로 접근해보면, missing number를 제외한 나머지 값은 다음 두 가지 경우에서 모두 발견된다.
27+
(1) 0 ~ n까지의 값
28+
(2) nums의 원소
29+
missing number는 이 두 가지 케이스 중 (1)에서만 한 번 발견되므로, 이를 XOR 연산으로 검출해 낼 수 있다.
30+
(짝수 번 등장한 값은 사라짐)
31+
"""
32+
33+
res = 0
34+
35+
# (1) 0 ~ n까지의 값 XOR
36+
for i in range(len(nums) + 1):
37+
res ^= i
38+
# (2) nums의 원소 XOR
39+
for n in nums:
40+
res ^= n
41+
42+
return res

‎reorder-list/seungriyou.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# https://leetcode.com/problems/reorder-list/
2+
3+
from typing import Optional
4+
5+
# Definition for singly-linked list.
6+
class ListNode:
7+
def __init__(self, val=0, next=None):
8+
self.val = val
9+
self.next = next
10+
11+
class Solution:
12+
def reorderList(self, head: Optional[ListNode]) -> None:
13+
"""
14+
Do not return anything, modify head in-place instead.
15+
"""
16+
"""
17+
[Complexity]
18+
- TC: O(n)
19+
- SC: O(1)
20+
21+
[Approach]
22+
1. linked-list의 중간 지점을 찾은 후, left / right list를 나눈다.
23+
2. right list를 reverse 한다.
24+
3. left와 right에서 노드를 하나씩 가져와 연결한다.
25+
"""
26+
27+
# linked-list의 중간 지점을 찾은 후, left / right list 나누기
28+
slow = fast = head
29+
while fast and fast.next:
30+
slow = slow.next # -> fast가 linked-list의 끝에 도달하면, slow는 중앙에 위치
31+
fast = fast.next.next
32+
33+
# (slow부터 시작하는) right list를 reverse (w. 다중 할당)
34+
prev, curr = None, slow
35+
while curr:
36+
curr.next, prev, curr = prev, curr, curr.next
37+
38+
# left와 right list에서 노드를 각각 하나씩 가져와 연결 (w. 다중 할당)
39+
left, right = head, prev
40+
while right.next:
41+
left.next, left = right, left.next
42+
right.next, right = left, right.next

0 commit comments

Comments
 (0)
Please sign in to comment.