Skip to content

[seungriyou] Week 13 Solutions #1607

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 5 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
41 changes: 41 additions & 0 deletions find-median-from-data-stream/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# https://leetcode.com/problems/find-median-from-data-stream/

import heapq

class MedianFinder:
"""
[Time Complexity]
- addNum(): O(logn) (heappush / heappop -> 힙 속성 유지 위해 트리 높이만큼 swap => sift-up / sift-down)
- findMedian(): O(1)

[Approach]
findMedian을 O(1)에 수행하기 위해서는 addNum을 할 때마다 데이터를 작은 부분 / 큰 부분으로 절반씩 나누어서 유지해야 한다.
이때, 가능한 케이스별 median을 구하는 방법은 다음과 같다.
- 두 절반의 길이가 같다면 median = ((작은 부분의 가장 큰 값) + (큰 부분의 가장 작은 값)) / 2
- 두 절반의 길이가 다르다면 (큰 부분의 길이가 작은 부분의 길이보다 1 큰 경우라고 가정) median = (큰 부분의 가장 작은 값)
따라서 작은 부분 / 큰 부분을 각각 max heap / min heap으로 관리하며, addNum 할 때마다 다음은 동작을 수행한다.
- 두 절반의 길이가 같다면, 작은 부분에 push -> pop 한 결과를 큰 부분에 push
- 두 절반의 길이가 다르다면, 큰 부분에 push -> pop 한 결과를 작은 부분에 push
(파이썬에서 max heap을 사용하기 위해서는 min heap에 부호 반전인 수를 넣음으로써 구현하는 것에 유의한다!)
"""

def __init__(self):
self.lo = [] # 절반 중 작은 부분
self.hi = [] # 절반 중 큰 부분

def addNum(self, num: int) -> None:
if len(self.lo) == len(self.hi):
heapq.heappush(self.hi, -heapq.heappushpop(self.lo, -num)) # heappushpop: heap을 한 번만
else:
heapq.heappush(self.lo, -heapq.heappushpop(self.hi, num))

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

# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()
41 changes: 41 additions & 0 deletions insert-interval/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# https://leetcode.com/problems/insert-interval/

from typing import List

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

[Approach]
intervals의 각 interval에 대해 다음의 케이스로 나눠볼 수 있다.
1) left에 해당하는 interval: left에 추가
2) right에 해당하는 interval: right에 추가
3) newInterval과 겹치는 interval: ns & ne 업데이트 (newInterval 확장)
"""
ns, ne = newInterval

# left: end < ns
# right: start > ne
left, right = [], []

for s, e in intervals:
# 1) left에 해당하는 interval이라면, left에 추가
if e < ns:
left.append([s, e])

# 2) right에 해당하는 interval이라면, right에 추가
elif s > ne:
right.append([s, e])

# 3) newInterval과 겹치는 interval이라면, ns & ne 업데이트
else:
ns = min(ns, s)
ne = max(ne, e)

# left.append([ns, ne])
# left.extend(right)
# return left
return left + [[ns, ne]] + right
92 changes: 92 additions & 0 deletions kth-smallest-element-in-a-bst/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# https://leetcode.com/problems/kth-smallest-element-in-a-bst/

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 kthSmallest_recur(self, root: Optional[TreeNode], k: int) -> int:
"""
[Complexity]
- TC: O(k)
- SC: O(height) (call stack)

[Approach]
BST를 inorder로 순회하면 오름차순으로 node를 방문할 수 있다. (recursive)
각 호출에서 cnt를 세며 k와 같을 때 res 값을 기록한다.
"""
cnt, res = 0, None

def inorder(node):
nonlocal cnt, res

# base condition
if not node or res:
return

# recur
inorder(node.left)
cnt += 1
if cnt == k:
res = node.val
inorder(node.right)

inorder(root)

return res

def kthSmallest_recur2(self, root: Optional[TreeNode], k: int) -> int:
"""
[Complexity]
- TC: O(k)
- SC: O(height) (call stack)

[Approach]
이전 recursive 풀이를 generator 방식으로 바꿀 수 있다. (yield from으로 recursion 구현)
"""

def inorder(node):
if node:
yield from inorder(node.left)
yield node
yield from inorder(node.right)

for i, node in enumerate(inorder(root), start=1):
if i == k:
return node.val

def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
"""
[Complexity]
- TC: O(k)
- SC: O(height) (stack에는 최대 height 개의 node가 들어감)

[Approach]
BST의 inorder 순회를 stack을 이용하여 iterative 하게 풀이할 수 있다.
"""
cnt, stack = 0, []

# root에서부터 left child를 stack에 넣기
while root:
stack.append(root)
root = root.left

# leaf left child 부터 stack에서 pop
while stack:
node = stack.pop()
cnt += 1

if cnt == k:
return node.val

# 현재 node의 right child가 있다면 stack에 넣고
right = node.right
while right:
# right child에서부터 left child를 stack에 넣기
stack.append(right)
right = right.left
54 changes: 54 additions & 0 deletions lowest-common-ancestor-of-a-binary-search-tree/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/

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

class Solution:
def lowestCommonAncestor_recur(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
"""
[Complexity]
- TC: O(height)
- SC: O(height) (call stack)

[Approach]
어떤 node와 두 노드 p, q 간의 관계를 다음의 케이스로 나누어 볼 수 있다.
1) p와 q가 모두 현재 node 보다 작다면 --> left subtree로 내려가 살펴보기
2) p와 q가 모두 현재 node 보다 크다면 --> right subtree로 내려가 살펴보기
3) p와 q가 현재 node의 두 child subtree에 각각 존재한다면 --> 현재 node가 p, q의 LCA
"""

def find_lca(node):
# 1) p와 q가 모두 현재 node 보다 작다면 --> left subtree로 내려가 살펴보기
if p.val < node.val > q.val:
return find_lca(node.left)
# 2) p와 q가 모두 현재 node 보다 크다면 --> right subtree로 내려가 살펴보기
if p.val > node.val < q.val:
return find_lca(node.right)
# 3) p와 q가 현재 node의 두 child subtree에 각각 존재한다면 --> 현재 node가 p, q의 LCA
return node

return find_lca(root)

def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
"""
[Complexity]
- TC: O(height)
- SC: O(1)

[Approach]
이전 recursive 풀이를 iterative 하게 풀이할 수 있다.
"""
while root:
# 1) p와 q가 모두 현재 node 보다 작다면 --> left subtree로 내려가 살펴보기
if p.val < root.val > q.val:
root = root.left
# 2) p와 q가 모두 현재 node 보다 크다면 --> right subtree로 내려가 살펴보기
elif p.val > root.val < q.val:
root = root.right
# 3) p와 q가 현재 node의 두 child subtree에 각각 존재한다면 --> 현재 node가 p, q의 LCA
else:
return root
23 changes: 23 additions & 0 deletions meeting-rooms/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# https://leetcode.com/problems/meeting-rooms/

from typing import List

class Solution:
def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
"""
[Complexity]
- TC: O(nlogn)
- SC: O(1) (inplace sorting)

[Approach]
intervals를 start 기준으로 오름차순 정렬 후, 앞 회의의 끝 시간 > 뒷 회의의 시작 시간이라면 겹치는 것이므로 False 반환
"""
# sort intervals (by start)
intervals.sort()

for i in range(1, len(intervals)):
# prev_e > curr_s 라면 False
if intervals[i - 1][1] > intervals[i][0]:
return False

return True