Skip to content

[hi-rachel] Week 13 solutions #1622

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 7 commits into from
Jun 28, 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
39 changes: 39 additions & 0 deletions find-median-from-data-stream/hi-rachel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 두 개로 나눠서 저장하면,
# 홀수개의 숫자일 경우: 첫 번째에서 가장 큰 값이나 두 번째에서 가장 작은 값이 중앙값
# 짝수개의 숫자일 경우: 첫 번째에서 가장 큰 값과 두 번째에서 가장 작은 값의 평균
# => 힙 활용
# 숫자 추가시 작은 값들 => 최대 힙, 큰 값들 => 최소 힙에 음수로 저장! 최대 힙의 모든 값 ≤ 최소 힙의 모든 값
# 최대 힙에서 가장 큰 값을 최소 힙으로 이동
# 만약 최소 힙 크기가 더 크면 다시 하나를 최대 힙으로 이동
# 두 힙간의 크기 차이 항상 0 또는 1 유지
# TC: addNum O(log n), findMedian O(1)
# SC: O(n)

import heapq

class MedianFinder:
def __init__(self):
self.max_heap = [] # 최대 힙 (음수로 저장)
self.min_heap = [] # 최소 힙

def addNum(self, num: int) -> None:
# 1. 최대 힙에 먼저 삽입 (음수로)
heapq.heappush(self.max_heap, -num)

# 2. max_heap에서 가장 큰 값을 min_heap으로 옮김 (오름차순 정렬 유지)
heapq.heappush(self.min_heap, -heapq.heappop(self.max_heap))

# 3. min_heap이 max_heap보다 커지면, 다시 max_heap으로 옮김 (균형 유지)
if len(self.min_heap) > len(self.max_heap):
heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap))

def findMedian(self) -> float:
if len(self.max_heap) == len(self.min_heap):
return (-self.max_heap[0] + self.min_heap[0]) / 2
else:
return -self.max_heap[0]

# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()
35 changes: 35 additions & 0 deletions insert-interval/hi-rachel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# https://leetcode.com/problems/insert-interval/
#
# 새로운 구간을 기존의 정렬된 구간 리스트에 삽입하고, 겹치는 구간을 병합
# 오름차순 정렬된 [start, end] 형태의 리스트.
# 새로운 interval [newStart, newEnd]를 삽입한 후에도 구간이 겹치지 않고 정렬된 상태로 유지.
#
# TC: O(N), 각 interval을 최대 한 번씩만 탐색
# SC: O(N)

from typing import List

class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
res = []
i = 0
n = len(intervals)

# 1. 왼쪽에 있는 겹치지 않는 구간
while i < n and intervals[i][1] < newInterval[0]:
res.append(intervals[i])
i += 1

# 2. 겹치는 구간 병합
while i < n and intervals[i][0] <= newInterval[1]:
newInterval[0] = min(newInterval[0], intervals[i][0])
newInterval[1] = max(newInterval[1], intervals[i][1])
i += 1
res.append(newInterval)

# 3. 오른쪽에 있는 겹치지 않는 구간
while i < n:
res.append(intervals[i])
i += 1

return res
25 changes: 25 additions & 0 deletions insert-interval/hi-rachel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
function insert(intervals: number[][], newInterval: number[]): number[][] {
const res: number[][] = [];
let i = 0;
const n = intervals.length;

// 1. newInterval보다 왼쪽에 있는 interval은 그대로 추가
while (i < n && intervals[i][1] < newInterval[0]) {
res.push(intervals[i]);
i++;
}
// 2. 겹치는 구간 병합
while (i < n && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(intervals[i][0], newInterval[0]);
newInterval[1] = Math.max(intervals[i][1], newInterval[1]);
i++;
}
res.push(newInterval);

// 3. 나머지 오른쪽 구간 추가
while (i < n) {
res.push(intervals[i]);
i++;
}
return res;
}
83 changes: 83 additions & 0 deletions kth-smallest-element-in-a-bst/hi-rachel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# BST에서 k번째로 작은 value를 반환해라
# 인덱스는 1에서 시작

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

# 풀이 1. Sort
# 모든 노드의 값을 오름차순으로 정렬 후에 k번째 값을 구한다.
# TC: O(N log N), N은 노드의 개수, 트리 순회 O(N), 정렬 O(N log N)
# SC: O(N), 모든 노드의 값을 저장하는 리스트

class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
values = []

def dfs(node):
if not node:
return

values.append(node.val)

dfs(node.left)
dfs(node.right)

dfs(root)

return sorted(values)[k - 1]

# 풀이 2. 힙 활용
# k번째로 작은 값 구하기 -> 최대 힙 활용
# - 요소를 -node.val 음수로 변환해 최대 힙 효과를 낸다.
# - 최대 힙에 값을 추가하다가, k개가 초과하면 최대 힙으로부터 최댓값을 제거
# => 트리 순회 종료 후, 최대 힙에는 가장 작은 k개 값만 남게 된다.
# TC: O(N log K), N은 노드의 개수, K는 찾고자 하는 값의 순서, 트리 순회 O(N), 최대 힙 삽입/제거 O(logK)
# SC: O(K), 최대 k개의 값을 저장하는 최대 힙

from heapq import heappush, heappop

class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
heap = [] # 최대 k개의 작은 값을 저장할 최대 힙

def dfs(node):
if not node:
return

heappush(heap, -node.val)
if len(heap) > k:
heappop(heap)

dfs(node.left)
dfs(node.right)

dfs(root)

# 힙의 최댓값이 k번째로 작은 값
return -heap[0]

# 풀이 3. 중위 순회 활용
# BST는 중위 순회를 하면 오름차순으로 노드에 접근할 수 있다
# => 입력 트리를 중위 순회 하면서 노드 값을 배열에 저장하면 자연스럽게 배열은 정렬됨
# TC: O(N), N은 노드의 개수, 트리 순회 O(N)
# SC: O(N), 모든 노드의 값을 저장하는 리스트

class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
values = []

def dfs(node):
if not node:
return
dfs(node.left)
values.append(node.val)
dfs(node.right)

dfs(root)
return values[k - 1]
48 changes: 48 additions & 0 deletions lowest-common-ancestor-of-a-binary-search-tree/hi-rachel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 주어진 BST와 노드 p, q가 주어질 때
# 이 둘의 가장 가까운 공통 조상(Lowest Common Ancestor, LCA)을 찾아 반환해라
# 공통 조상이란, p와 q 모두의 조상 중에서 가장 깊은 노드를 의미
#
# BST(Binary Search Tree)의 특징
# - 각 노드는 최대 두 개의 자식 노드를 가질 수 있음
# - 왼쪽 서브트리의 모든 값은 현재 노드보다 작고,
# - 오른쪽 서브트리의 모든 값은 현재 노드보다 큼.
#
# 풀이
# 현재 노드가 p, q보다 둘 다 작으면, 오른쪽 서브트리에서 LCA를 찾고,
# 둘 다 크면, 왼쪽 서브 트리에서 찾고,
# 한쪽은 작고, 한쪽은 크면 현재 노드가 LCA가 됨.

# TC: O(H), H는 이진 검색 트리의 높이
# SC: O(H), 재귀 스택에 필요한 공간

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

# 재귀 풀이
# TC: O(H), H는 이진 검색 트리의 높이
# SC: O(H), 재귀 스택에 필요한 공간
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if p.val < root.val and q.val < root.val:
return self.lowestCommonAncestor(root.left, p, q)
if root.val < p.val and root.val < q.val:
return self.lowestCommonAncestor(root.right, p, q)
return root

# 반복 풀이
# TC: O(H), H는 이진 검색 트리의 높이
# SC: O(1)
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
node = root
while node:
if p.val < node.val and q.val < node.val:
node = node.left
elif node.val < p.val and node.val < q.val:
node = node.right
else:
return node
22 changes: 22 additions & 0 deletions meeting-rooms/hi-rachel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# https://leetcode.com/problems/meeting-rooms/
# 주어진 회의 시간 리스트에서 모든 회의가 겹치지 않고 진행될 수 있는지 확인
# 회의 시간은 [start, end] 형태의 리스트로 주어짐
# 회의 시간이 겹치는 경우 false, 겹치지 않는 경우 true 반환

# TC: O(N log N), N은 회의의 개수, 회의 시간 정렬 O(N log N)
# SC: O(1)

from typing import List

class Solution:
def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
intervals.sort(key=lambda x: x[0])
for i in range(1, len(intervals)):
# 현재 회의의 시작 시간이 이전 회의의 종료 시간보다 작으면 겹침
if intervals[i][0] < intervals[i - 1][1]:
return False
return True

# sol = Solution()
# print(sol.canAttendMeetings([[0, 30], [5, 10], [15, 20]]))
# print(sol.canAttendMeetings([[7, 10], [2, 4]]))
13 changes: 13 additions & 0 deletions meeting-rooms/hi-rachel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// https://leetcode.com/problems/meeting-rooms/
// TC: O(N log N), N은 회의의 개수, 회의 시간 정렬 O(N log N)
// SC: O(1)

function canAttendMeetings(intervals: number[][]): boolean {
intervals.sort((a, b) => a[0] - b[0]);
for (let i = 1; i < intervals.length; i++) {
if (intervals[i][0] < intervals[i - 1][1]) {
return false;
}
}
return true;
}