Skip to content

Commit 0e40580

Browse files
committed
solutions
1 parent 5c74619 commit 0e40580

File tree

5 files changed

+398
-0
lines changed

5 files changed

+398
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""TC: O(), SC: O()
2+
3+
4+
아이디어:
5+
-
6+
7+
SC:
8+
-
9+
10+
TC:
11+
-
12+
"""
13+
14+
15+
# Definition for a binary tree node.
16+
# class TreeNode:
17+
# def __init__(self, val=0, left=None, right=None):
18+
# self.val = val
19+
# self.left = left
20+
# self.right = right
21+
class Solution:
22+
def maxPathSum(self, root: Optional[TreeNode]) -> int:
23+
sol = [-1001] # 노드의 최소값보다 1 작은 값. 현 문제 세팅에서 -inf 역할을 함.
24+
25+
def try_get_best_path(node):
26+
if node is None:
27+
# 노드가 비어있을때 경로 없음. 이때 이 노드로부터 얻을 수 있는 최대 경로 값을
28+
# 0으로 칠 수 있다.
29+
return 0
30+
31+
# 왼쪽, 오른쪽 노드로부터 얻을 수 있는 최대 경로 값.
32+
l = max(try_get_best_path(node.left), 0)
33+
r = max(try_get_best_path(node.right), 0)
34+
35+
# 현 노드를 다리 삼아서 양쪽 자식 노드의 경로를 이었을때 나올 수 있는 경로 값이
36+
# 최대 경로일 수도 있다. 이 값을 현 솔루션과 비교해서 업데이트 해준다.
37+
sol[0] = max(node.val + l + r, sol[0])
38+
39+
# 현 노드의 부모 노드가 `이 노드를 통해 얻을 수 있는 최대 경로 값`으로 사용할 값을 리턴.
40+
return node.val + max(l, r)
41+
42+
try_get_best_path(root)
43+
return sol[0]

graph-valid-tree/haklee.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"""
2+
아이디어:
3+
- 트리여야 하므로 엣지 개수가 n-1개여야 한다.
4+
- 엣지 개수가 n-1개이므로 만약 중간에 사이클이 있다면 트리가 연결이 안 된다.
5+
- 연결이 안 되었으니 트리는 아니고... 몇 조각으로 쪼개진 그래프가 된다. 여튼, valid tree가
6+
아니게 된다.
7+
- spanning tree 만들 때를 생각해보자. 엣지 하나를 더할 때마다 노드가 하나씩 트리에 추가되어야
8+
엣지 n-1개로 노드 n개를 겨우 만들 수 있는데, 중간에 새로운 노드를 추가 안하고 엄한 곳에
9+
엣지를 써서 사이클을 만들거나 하면 모든 노드를 연결할 방법이 없다.
10+
- 위의 예시보다 좀 더 일반적으로는 union-find 알고리즘에서 설명하는 union 시행으로도 설명이
11+
가능하다. union-find에서는 처음에 n개의 노드들의 parent가 자기 자신으로 세팅되어 있는데,
12+
즉, 모든 노드들이 n개의 그룹으로 나뉘어있는데, 여기서 union을 한 번 시행할 때마다 그룹이 1개
13+
혹은 0개 줄어들 수 있다. 그런데 위 문제에서는 union을 엣지 개수 만큼, 즉, n-1회 시행할 수 있으므로,
14+
만약 union 시행에서 그룹의 개수가 줄어들지 않는 경우(즉, 엣지 연결을 통해 사이클이 생길 경우)가
15+
한 번이라도 발생하면 union 시행 후 그룹의 개수가 2 이상이 되어 노드들이 서로 연결되지 않아 트리를
16+
이루지 못한다.
17+
"""
18+
19+
"""TC: O(), SC: O()
20+
21+
n은 주어진 노드의 개수, e는 주어진 엣지의 개수.
22+
23+
아이디어(이어서):
24+
- union-find 아이디어를 그대로 활용한다.
25+
- 나이브한 접근:
26+
- union을 통해서 엣지로 연결된 두 집합을 합친다.
27+
- find를 통해서 0번째 노드와 모든 노드들이 같은 집합에 속해있는지 확인한다.
28+
- 더 좋은 구현:
29+
- union 시행 중 같은 집합에 속한 두 노드를 합치려고 하는 것을 발견하면 False 리턴
30+
31+
SC:
32+
-
33+
34+
TC:
35+
-
36+
"""
37+
38+
39+
class Solution:
40+
"""
41+
@param n: An integer
42+
@param edges: a list of undirected edges
43+
@return: true if it's a valid tree, or false
44+
"""
45+
46+
def valid_tree(self, n, edges):
47+
# write your code here
48+
49+
# union find
50+
parent = list(range(n))
51+
52+
def find(x):
53+
if x == parent[x]:
54+
return x
55+
56+
parent[x] = find(parent[x])
57+
return parent[x]
58+
59+
def union(a, b):
60+
pa = find(a)
61+
pb = find(b)
62+
parent[pb] = pa
63+
64+
# 원래는 값을 리턴하지 않아도 되지만, 같은 집합에 속한 노드를
65+
# union하려는 상황을 판별하기 위해 값 리턴.
66+
return pa == pb
67+
68+
if len(edges) != n - 1:
69+
# 트리에는 엣지가 `(노드 개수) - 1`개 만큼 있다.
70+
# 이 조건 만족 안하면 커팅.
71+
return False
72+
73+
# 나이브한 구현:
74+
# - 모든 엣지로 union 시행
75+
# - find로 모든 노드가 0번 노드와 같은 집합에 속해있는지 확인
76+
77+
# for e in edges:
78+
# union(*e)
79+
80+
# return all(find(0) == find(i) for i in range(n))
81+
82+
# 더 좋은 구현:
83+
# - union 시행 중 같은 집합에 속한 두 노드를 합치려고 하는 것을 발견하면 False 리턴
84+
for e in edges:
85+
if union(*e):
86+
return False
87+
88+
return True
89+
90+
91+
"""TC: O(), SC: O()
92+
93+
n은 주어진 노드의 개수, e는 주어진 엣지의 개수.
94+
95+
아이디어(이어서):
96+
- union-find를 쓰면 union을 여러 번 시행해야 하는데 이 과정에서 시간을 많이 잡아먹는것 같다.
97+
- 트리를 잘 이뤘는지 확인하려면 한 노드에서 시작해서 dfs를 돌려서 모든 노드들에 도달 가능한지
98+
체크하면 되는데, 이게 시간복잡도에 더 유리하지 않을까?
99+
100+
SC:
101+
-
102+
103+
TC:
104+
-
105+
"""
106+
107+
108+
class Solution:
109+
"""
110+
@param n: An integer
111+
@param edges: a list of undirected edges
112+
@return: true if it's a valid tree, or false
113+
"""
114+
115+
def valid_tree(self, n, edges):
116+
# write your code here
117+
if len(edges) != n - 1:
118+
# 트리에는 엣지가 `(노드 개수) - 1`개 만큼 있다.
119+
# 이 조건 만족 안하면 커팅.
120+
return False
121+
122+
adj_list = [[] for _ in range(n)]
123+
for a, b in edges:
124+
adj_list[a].append(b)
125+
adj_list[b].append(a)
126+
127+
visited = [False for _ in range(n)]
128+
129+
def dfs(node):
130+
visited[node] = True
131+
for adj in adj_list[node]:
132+
if not visited[adj]:
133+
dfs(adj)
134+
135+
# 한 노드에서 출발해서 모든 노드가 visted 되어야 주어진 엣지들로 트리를 만들 수 있다.
136+
# 아무 노드에서나 출발해도 되는데 0번째 노드를 선택하자.
137+
dfs(0)
138+
139+
return all(visited)

insert-interval/haklee.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""TC: O(n), SC: O(1)
2+
3+
n은 intervals로 주어진 인터벌의 개수.
4+
5+
아이디어:
6+
- 주어진 인터벌들을 앞에서부터 순회하면서 새 인터벌(newInterval)과 겹치는지 보고,
7+
- 겹치면 합친다. 합친 인터벌로 newInterval을 업데이트 한다.
8+
- 안 겹치면 newInterval과 현재 확인 중인 인터벌(curInterval) 중에 필요한 인터벌을
9+
결과 리스트에 넣어주어야 한다.
10+
- 안 겹치면,
11+
- 이때, curInterval이 newInterval보다 앞에 있으면 이후 인터벌들 중 newInterval과 합쳐야 하는
12+
인터벌이 존재할 수 있다. newInterval은 건드리지 않고 curInterval만 결과 리스트에 넣는다.
13+
- curInterval이 newInterval보다 뒤에 있으면 newInterval을 결과 리스트에 더해주고, 그 다음
14+
curInterval도 결과 리스트에 더해주어야 한다.
15+
- 그런데 curInterval이 들어있는 리스트가 정렬되어 있으므로, 이후에 순회할 curInterval
16+
중에는 더 이상 newInterval과 겹칠 인터벌이 없다. newInterval은 이제 더 이상 쓰이지
17+
않으므로 None으로 바꿔준다.
18+
19+
SC:
20+
- newInterval 값만 업데이트 하면서 관리. O(1).
21+
22+
TC:
23+
- intervals에 있는 아이템을 순회하면서 매번 체크하는 시행이 O(1).
24+
- 위의 시행을 intervals에 있는 아이템 수만큼 진행하므로 O(n).
25+
"""
26+
27+
28+
class Solution:
29+
def insert(
30+
self, intervals: List[List[int]], newInterval: List[int]
31+
) -> List[List[int]]:
32+
res = []
33+
for curInterval in intervals:
34+
if newInterval:
35+
# 아직 newInterval이 None으로 변경되지 않았다.
36+
if curInterval[1] < newInterval[0]:
37+
# cur, new가 겹치지 않고, curInterval이 더 앞에 있음.
38+
res.append(curInterval)
39+
elif curInterval[0] > newInterval[1]:
40+
# cur, new가 겹치지 않고, newInterval이 더 앞에 있음.
41+
res.append(newInterval)
42+
res.append(curInterval)
43+
newInterval = None
44+
else:
45+
# 겹치는 부분 존재. newInterval을 확장한다.
46+
newInterval = [
47+
min(curInterval[0], newInterval[0]),
48+
max(curInterval[1], newInterval[1]),
49+
]
50+
else:
51+
# 더 이상 newInterval과 연관된 작업을 하지 않는다. 순회 중인
52+
# curInterval을 결과 리스트에 더하고 끝.
53+
res.append(curInterval)
54+
55+
if newInterval:
56+
# intervals에 있는 마지막 아이템이 newInterval과 겹쳤을 경우 아직
57+
# 결과 리스트에 newInterval이 더해지지 않고 앞선 순회가 종료되었을
58+
# 수 있다. 이 경우 newInterval이 아직 None이 아니므로 리스트에 더해준다.
59+
res.append(newInterval)
60+
61+
return res
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""TC: O(n), SC: O(h)
2+
3+
h는 주어진 트리의 높이, n은 주어진 트리의 노드 개수.
4+
5+
아이디어:
6+
특정 노드의 깊이는 `max(오른쪽 깊이, 왼쪽 깊이) + 1`이다. 이렇게 설명하자니 부모 노드의 깊이 값이
7+
자식의 깊이 값보다 더 큰 것이 이상하긴 한데... 큰 맥락에서 무슨 말을 하고 싶은지는 이해가 가능하다고
8+
본다.
9+
10+
SC:
11+
- 호출 스택은 트리의 높이(...혹은 깊이)만큼 커진다. O(h).
12+
13+
TC:
14+
- 모든 노드를 방문한다. O(n).
15+
"""
16+
17+
18+
# Definition for a binary tree node.
19+
# class TreeNode:
20+
# def __init__(self, val=0, left=None, right=None):
21+
# self.val = val
22+
# self.left = left
23+
# self.right = right
24+
class Solution:
25+
def maxDepth(self, root: Optional[TreeNode]) -> int:
26+
def get_depth(node: Optional[TreeNode]) -> int:
27+
return max(get_depth(node.left), get_depth(node.right)) + 1 if node else 0
28+
29+
return get_depth(root)

0 commit comments

Comments
 (0)