Skip to content

[seungriyou] Week 12 Solutions #1597

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 6 commits into from
Jun 20, 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
57 changes: 57 additions & 0 deletions non-overlapping-intervals/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# https://leetcode.com/problems/non-overlapping-intervals/

from typing import List

class Solution:
def eraseOverlapIntervals1(self, intervals: List[List[int]]) -> int:
"""
[Complexity]
- TC: O(nlogn) (sort)
- SC: O(1) (tim sort -> 최악의 경우 O(n)까지 가능)

[Approach]
intervals를 start 기준으로 오름차순 정렬 후, greedy 하게 항상 작은 end를 가지는 interval을 선택한다.
"""
# sort asc by start
intervals.sort(key=lambda x: x[0])

# initialize prev_e
prev_e = intervals[0][0] - 1
to_be_removed = 0

for s, e in intervals:
# (1) overlapping : greedy하게 prev_e와 e 중 더 작은 값으로 prev_e를 업데이트하고, 제거할 interval 수 증가
if prev_e > s:
prev_e = min(prev_e, e)
to_be_removed += 1
# (2) non-overlapping : prev_e만 업데이트
else:
prev_e = e

return to_be_removed

def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
"""
[Complexity]
- TC: O(nlogn) (sort)
- SC: O(1) (tim sort -> 최악의 경우 O(n)까지 가능)

[Approach]
이전 풀이의 (1) overlapping 단계에서 prev_e와 e 중 min을 고르는 로직을 제거하려면, intervals를 end 기준으로 오름차순 정렬하면 된다.
"""
# sort asc by end
intervals.sort(key=lambda x: x[1])

# initialize prev_e
prev_e = intervals[0][0] - 1
to_be_removed = 0

for s, e in intervals:
# (1) overlapping : greedy하게 prev_e와 e 중 더 작은 값으로 선택하면 되므로 prev_e를 그대로 두고, 제거할 interval 수 증가
if prev_e > s:
to_be_removed += 1
# (2) non-overlapping : prev_e만 업데이트
else:
prev_e = e

return to_be_removed
108 changes: 108 additions & 0 deletions number-of-connected-components-in-an-undirected-graph/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/

from typing import List

class Solution:
def countComponents_bfs(self, n: int, edges: List[List[int]]) -> int:
"""
[Complexity]
- TC: O(v + e) (모든 edge & node 한 번씩 탐색)
- SC: O(v + e) (graph)

[Approach]
BFS로 visited를 기록해가며 connected component를 센다.
"""
from collections import deque

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

visited, res = set(), 0

def bfs(start):
q = deque([start])
visited.add(start)

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

return

for i in range(n):
if i not in visited:
bfs(i)
res += 1

return res

def countComponents_dfs(self, n: int, edges: List[List[int]]) -> int:
"""
[Complexity]
- TC: O(v + e) (모든 edge & node 한 번씩 탐색)
- SC: O(v + e) (graph)

[Approach]
DFS로 visited를 기록해가며 connected component를 센다.
"""
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)

visited, res = set(), 0

def dfs(pos):
# 이전에 visited 포함 여부 확인하므로 base condition 생략 가능

visited.add(pos)

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

return

for i in range(n):
if i not in visited:
dfs(i)
res += 1

return res

def countComponents(self, n: int, edges: List[List[int]]) -> int:
"""
[Complexity]
- TC: O(v + e * α(v)) (모든 edge & node 한 번씩 탐색)
- SC: O(v) (parent, set(...))

[Approach]
edges를 iterate 하며 union-find 수행 후, parent의 종류의 개수를 세면 된다.
parent의 종류의 개수를 셀 때는 다시 find_parent(x)로 찾아야 한다!
"""

def find_parent(x):
if x != parent[x]:
parent[x] = find_parent(parent[x])
return parent[x]

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

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

parent = [i for i in range(n)]

for x, y in edges:
union_parent(x, y)

return len(set(find_parent(i) for i in range(n)))
133 changes: 133 additions & 0 deletions remove-nth-node-from-end-of-list/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# https://leetcode.com/problems/remove-nth-node-from-end-of-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 removeNthFromEnd_recur(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
"""
[Complexity]
- TC: O(len)
- SC: O(len) (call stack)

[Approach]
재귀적으로 linked list를 확인하며 끝에서부터의 순서를 확인 후, 다음 node의 순서가 n과 같을 때 다음 node를 건너뛰도록 한다.
head node를 제거해야 하는 경우(= 끝에서부터 n번째인 node가 head인 경우)에는, 재귀 함수 내에서 node 제거가 불가능하므로 head.next를 반환한다.
"""

def check_order_from_end(curr):
# base condition
if not curr.next:
return 1

# recur
next_order_from_end = check_order_from_end(curr.next)
# nth from end인 node를 건너뛰기
if next_order_from_end == n:
curr.next = curr.next.next

return next_order_from_end + 1

# head node를 제거해야 하는 경우라면, check_order_from_end() 내에서 node 제거가 불가능하므로 head.next를 반환해야 함
if check_order_from_end(head) == n:
return head.next

return head

def removeNthFromEnd_recur2(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
"""
[Complexity]
- TC: O(len)
- SC: O(len) (call stack)

[Approach]
이전 풀이에서 head node를 제거해야 하는 경우를 따로 처리하지 않으려면, 주어진 head를 가리키는 dummy node(= prev)를 추가하고
dummy node의 next node를 반환하면 된다.
"""

def check_order_from_end(curr):
# base condition
if not curr.next:
return 1

# recur
next_order_from_end = check_order_from_end(curr.next)
# nth from end인 node를 건너뛰기
if next_order_from_end == n:
curr.next = curr.next.next

return next_order_from_end + 1

prev = ListNode(next=head)
check_order_from_end(prev)

return prev.next

def removeNthFromEnd_length(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
"""
[Complexity]
- TC: O(n)
- SC: O(1)

[Approach]
linked list의 전체 길이를 구하고, head에서부터 (길이 - n - 1) 번 전진하여 node를 건너뛰면 된다.
(2 pass)
"""
# linked list의 length 구하기
length = 0
curr = head
while curr:
length += 1
curr = curr.next

# length == n라면, head를 제거
if length == n:
return head.next

# length - n - 1 번 이동
curr = head
for _ in range(length - n - 1):
curr = curr.next

# node 제거
curr.next = curr.next.next

return head

def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
"""
[Complexity]
- TC: O(len)
- SC: O(1)

[Approach]
slow, fast의 two pointer를 이용해 반복문으로 풀 수 있다. (1 pass)
1. fast를 n 번 전진
2. fast가 끝에 도달한 경우, 첫 번째 node를 제거해야하므로 head.next 반환
3. 현재 fast의 위치에서 slow와 fast를 함께 전진하면, fast가 끝에 도달할 때 slow는 뒤에서부터 n + 1번째 node임
4. 뒤에서부터 n + 1번째인 node가 n - 1번째 node를 가리키도록 함
"""
slow = fast = head

# 1. fast를 n 번 전진
for _ in range(n):
fast = fast.next

# 2. fast가 끝에 도달한 경우, 첫 번째 node를 제거해야하므로 head.next 반환
if not fast:
return head.next

# 3. 현재 fast의 위치에서 slow와 fast를 함께 전진하면,
# fast가 끝에 도달할 때 slow는 뒤에서부터 n + 1번째 node임
while fast.next:
slow, fast = slow.next, fast.next

# 4. 뒤에서부터 n + 1번째인 node가 n - 1번째 node를 가리키도록 함
slow.next = slow.next.next

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

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 isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
"""
[Complexity]
- TC: O(n)
- SC: O(height) (call stack)

[Approach]
재귀적으로 두 tree를 타고 내려가며 확인할 수 있다.
각 단계에서 두 tree가 다르다고 판단할 수 있는 조건은
(1) 한 쪽 node만 None이거나
(2) 두 node의 값이 다른
경우이다.
"""
# base condition
if not p and not q:
return True

# not same
if not p or not q or p.val != q.val:
return False

return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
Loading