Skip to content

[seungriyou] Week 14 Solutions #1635

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
Jul 5, 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
73 changes: 73 additions & 0 deletions binary-tree-level-order-traversal/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# https://leetcode.com/problems/binary-tree-level-order-traversal/

from typing import Optional, List

# 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 levelOrder_bfs(self, root: Optional[TreeNode]) -> List[List[int]]:
"""
[Complexity]
- TC: O(n)
- SC: O(width) (최악의 경우 O(n)) (결과 res 제외)

[Approach]
레벨 별로 BFS로 접근
"""
if not root:
return []

res, level = [], [root]

while level:
# res에 현재 level 내 node value 추가
res.append([node.val for node in level])

# next level의 node로 level 업데이트
level = [child for node in level for child in (node.left, node.right) if child]
# next_level = []
# for node in level:
# if node.left:
# next_level.append(node.left)
# if node.right:
# next_level.append(node.right)
# level = next_level

return res

def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
"""
[Complexity]
- TC: O(n)
- SC: O(height) (call stack) (결과 res 제외)

[Approach]
레벨 별로 DFS로 접근
"""
if not root:
return []

res = []

def dfs(node, level):
# base condition (각 level의 첫 시작)
if len(res) == level:
res.append([])

# 현재 level에 node.val 추가
res[level].append(node.val)

# node의 child에 대해 다음 level로 진행
if node.left:
dfs(node.left, level + 1)
if node.right:
dfs(node.right, level + 1)

dfs(root, 0)

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

from typing import List

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

[Approach]
다음과 같은 규칙을 찾을 수 있다.

0 ~ 1 bin(0) = 0
bin(1) = 1

2 ~ 3 bin(2) = 10 = 10 + bin(0)
bin(3) = 11 = 10 + bin(1)
=> offset = 10(2) = 2 (-> 1이 1개)

4 ~ 7 bin(4) = 100 = 100 + bin(0)
bin(5) = 101 = 100 + bin(1)
bin(6) = 110 = 100 + bin(2)
bin(7) = 111 = 100 + bin(3)
=> offset = 100(2) = 4 (-> 1이 1개)

8 ~ 15 bin(8) = 1000 = 1000 + bin(0)
...
bin(15) = 1111 = 1000 + bin(7)
=> offset = 1000(2) = 8 (-> 1이 1개)
"""

dp = [0] * (n + 1)
offset = 1

for i in range(1, n + 1):
# i가 2의 제곱이면, offset 2배
if offset * 2 == i:
offset *= 2

# 이전에 구한 값 사용
dp[i] = dp[i - offset] + 1

return dp

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

dp = [0] * (n + 1)
for i in range(1, n + 1):
# dp[i] = dp[i // 2] + (i % 2)
dp[i] = dp[i >> 1] + (i & 1)

return dp
65 changes: 65 additions & 0 deletions house-robber-ii/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# https://leetcode.com/problems/house-robber-ii/

from typing import List

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

[Approach]
집들이 원을 이루고 있고 인접한 두 집을 모두 방문하면 안 되기 때문에, 다음과 같이 두 가지 상황으로 max money를 구한다.
(원형이라는 점을 고려하지 않으면 첫 번째 집과 마지막 집을 모두 방문하게 될 수도 있기 때문)
(1) 첫 번째 집을 제외
(2) 마지막 집을 제외
그리고 두 값 중 큰 값을 반환한다.

dp[i] = nums[i]까지 확인했을 때의 max money
dp[i] = max(dp[i - 1], dp[i - 2] + num)
"""
n = len(nums)

# early stop
if n <= 3:
return max(nums)

dp1 = [0] * (n - 1) # 마지막 집 제외: nums[0] ~ nums[n - 2]
dp2 = [0] * (n - 1) # 첫 번째 집 제외: nums[1] ~ nums[n - 1]

# initialize
dp1[0], dp2[0] = nums[0], nums[1]
dp1[1], dp2[1] = max(dp1[0], nums[1]), max(dp2[0], nums[2])

for i in range(2, n - 1):
dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i])
dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i + 1])

return max(dp1[-1], dp2[-1])

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

[Approach]
이전 O(n) space DP 풀이에서 dp[i] 값을 구하기 위해 dp[i - 1] & dp[i - 2] 값만 참고하므로,
O(1) space로 optimize 할 수 있다.
"""
n = len(nums)

# early stop
if n <= 3:
return max(nums)

# p2 = dp[i - 2], p1 = dp[i - 1]
f_p2 = f_p1 = 0 # 마지막 집 제외: nums[0] ~ nums[n - 2]
l_p2 = l_p1 = 0 # 첫 번째 집 제외: nums[1] ~ nums[n - 1]

for i in range(n - 1):
f_p2, f_p1 = f_p1, max(f_p1, f_p2 + nums[i])
l_p2, l_p1 = l_p1, max(l_p1, l_p2 + nums[i + 1])

return max(f_p1, l_p1)
66 changes: 66 additions & 0 deletions meeting-rooms-ii/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# https://leetcode.com/problems/meeting-rooms-ii/

from typing import List

class Solution:
def minMeetingRooms_heap(self, intervals: List[List[int]]) -> int:
"""
[Complexity]
- TC: O(nlogn) (sort & heappop/push)
- SC: O(n) (min heap)

[Approach]
기본적으로 회의 시작 시각이 빠른 순서로 살펴보아야 한다.
시작 시각 이전에 어떤 회의실이 빈다면 해당 회의실을 이어서 사용할 수 있고
시작 시각 이후까지 기존 회의가 마치지 않는다면 해당 회의실을 사용할 수 없다.
이를 매번 빠르게 판단하기 위해서는 그리디하게
"이전까지 회의실을 사용하던 회의 중 가장 빨리 마치는 회의의 종료 시각"과 "현재 보고 있는 회의 시작 시각"
을 비교하면 된다.
항상 회의의 종료 시각 중 가장 작은 값을 고르면 되므로, min heap을 이용하여 최솟값을 O(1)에 조회할 수 있도록 할 수 있다.
"""
import heapq

# 회의 시작 시각 순으로 오름차순 정렬
intervals.sort()

# min heap
rooms = []

for s, e in intervals:
# "이전까지 회의실을 사용하던 회의 중 가장 빨리 마치는 회의의 종료 시각"과 "현재 보고 있는 회의 시작 시각"이 겹치지 않는 경우,
# rooms에 존재하는 회의실 중 e가 가장 이른 회의실 pop
if rooms and rooms[0] <= s:
heapq.heappop(rooms)

# 현재 회의 push
heapq.heappush(rooms, e)

return len(rooms)

def minMeetingRooms(self, intervals: List[List[int]]) -> int:
"""
[Complexity]
- TC: O(nlogn)
- SC: O(n)

[Approach]
start와 end를 나누어 정렬한다면, 각각의 원소를 가리키는 two-pointer를 이용해
회의실을 재사용할 수 있는 경우와 새로운 회의실이 필요한 경우를 판단할 수 있다.
starts를 순회하면서 e라는 pointer로 ends의 첫번째 원소부터 비교하면 다음과 같이 케이스를 나눌 수 있다.
- non-overlap(ends[e] <= s): 회의실 재사용 가능 (e++를 통해 다음 회의 살펴보기)
- overlap(ends[e] > s): 새로운 회의실 필요
"""
starts = sorted(s for s, _ in intervals)
ends = sorted(e for _, e in intervals)

res = e = 0

for s in starts:
# non overlap -> 회의실 재사용 가능 (다음 회의 살펴보기)
if ends[e] <= s:
e += 1
# overlap -> 새로운 회의실 필요
else:
res += 1

return res
Loading