Skip to content

[EGON] Week13 Solutions #586

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
Nov 10, 2024
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
67 changes: 67 additions & 0 deletions find-median-from-data-stream/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from heapq import heappush, heappop
from unittest import TestCase, main


class MedianFinder:
"""
Runtime: 137 ms (Beats 56.16%)
Time Complexity:
1) addNum
- 최악의 경우 heappush + (heappop + heappush) + (heappop + heapppush) 가 발생할 수 있으므로 O(5 * log n)
> O(5 * log n) ~= O(log n)
2) findMedian
> heap의 루트 값을 가지고 사칙연산만 하므로 O(1)

Memory: 39.94 MB (Beats 5.85%)
Space Complexity: O(n)
- max_heap은 최대 n // 2 개 혹은 n // 2 + 1개의 원소를 가지므로 O(n / 2 + 1), upper bound
- min_heap은 최대 n // 2개의 원소를 가지므로 O(n / 2)
> O(n / 2 + 1) + O(n / 2) ~= O(n) + O(n) ~= O(n)
"""

def __init__(self):
self.max_heap = []
self.min_heap = []

def addNum(self, num):
heappush(self.max_heap, -num)
if self.min_heap and (-self.max_heap[0] > self.min_heap[0]):
heappush(self.min_heap, -heappop(self.max_heap))

if len(self.max_heap) > len(self.min_heap) + 1:
heappush(self.min_heap, -heappop(self.max_heap))
elif len(self.max_heap) < len(self.min_heap):
heappush(self.max_heap, -heappop(self.min_heap))

def findMedian(self):
if self.max_heap and self.min_heap:
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]
elif self.min_heap and not self.max_heap:
return self.min_heap[0]
elif not self.min_heap and self.max_heap:
return -self.max_heap[0]
else:
return 0.0


class _LeetCodeTestCases(TestCase):

def test_1(self):
medianFinder = MedianFinder()
medianFinder.addNum(-1)
self.assertEqual(medianFinder.findMedian(), -1.0)
medianFinder.addNum(-2)
self.assertEqual(medianFinder.findMedian(), -1.5)
medianFinder.addNum(-3)
self.assertEqual(medianFinder.findMedian(), -2.0)
medianFinder.addNum(-4)
self.assertEqual(medianFinder.findMedian(), -2.5)
medianFinder.addNum(-5)
self.assertEqual(medianFinder.findMedian(), -3.0)


if __name__ == '__main__':
main()
42 changes: 42 additions & 0 deletions house-robber/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from typing import List
from unittest import TestCase, main


class Solution:
def rob(self, nums: List[int]) -> int:
return self.solve_dp(nums)

"""
Runtime: 0 ms (Beats 100.00%)
Time Complexity: O(n)
- nums 배열을 조회하며 dp 배열을 갱신하므로 O(n)
- 2항에 대한 max 연산을 사용하므로 * O(2)
> O(2 * n) ~= O(n)

Memory: 16.62 MB (Beats 24.05%)
Space Complexity: O(n)
> 길이가 n인 dp 배열을 사용하므로 O(n)
"""

def solve_dp(self, nums: List[int]) -> int:
if len(nums) <= 2:
return max(nums)

dp = [0] * len(nums)
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, len(nums)):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])

return dp[-1]


class _LeetCodeTestCases(TestCase):
def test_1(self):
nums = [2,1,1,2]
output = 4
self.assertEqual(Solution().rob(nums), output)


if __name__ == '__main__':
main()
106 changes: 106 additions & 0 deletions lowest-common-ancestor-of-a-binary-search-tree/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from collections import deque
from unittest import TestCase, main


# Definition of TreeNode:
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None


class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
return self.solve_bfs(root, p, q)

"""
Runtime: 50 ms (Beats 81.68%)
Time Complexity:`
- bfs를 이용해 ancetor와 nodes를 초기화 하는데 트리의 모든 p와 q를 포함할 때까지 모든 node를 조회하는데, O(n)
- p로 부터 부모를 따라 올라가는데, 트리의 모든 node가 편향적으로 연결된 경우 최대 O(n), upper bound
- q로 부터 부모를 따라 올라가는데, 단, 위 p의 추적 path와 겹치지 않는 곳만 올라가므로, 총합이 O(n)
> O(n) + (O(P) + O(Q)) = O(n) + O(n) ~= O(n)

Memory: 21.20 MB (Beats 14.40%)
Space Complexity: O(n)
- p, q가 트리의 리프노드인 경우 dq의 크기는 O(n), upper bound
- p_ancestors와 q_anscestor의 크기는 합쳐서 O(n)
> O(n) + O(n) ~= O(n)
"""
def solve_bfs(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
dq = deque([root])
ancestor = {root.val: root.val}
nodes = {root.val: root}
while dq:
if p.val in ancestor and q.val in ancestor:
break

curr_node = dq.popleft()
if curr_node.left:
ancestor[curr_node.left.val] = curr_node.val
dq.append(curr_node.left)
nodes[curr_node.left.val] = curr_node.left
if curr_node.right:
ancestor[curr_node.right.val] = curr_node.val
dq.append(curr_node.right)
nodes[curr_node.right.val] = curr_node.right

p_val = p.val
p_ancestors = set()
p_ancestors.add(root.val)
while p_val in ancestor and p_val != root.val:
p_ancestors.add(p_val)
p_ancestors.add(ancestor[p_val])
p_val = ancestor[p_val]

q_val = q.val
q_ancestors = set()
q_ancestors.add(q_val)
while q_val in ancestor and q_val not in p_ancestors:
q_ancestors.add(q_val)
q_ancestors.add(ancestor[q_val])
q_val = ancestor[q_val]

common_ancestor = p_ancestors & q_ancestors
if common_ancestor:
return nodes[common_ancestor.pop()]
else:
return root


class _LeetCodeTestCases(TestCase):

def test_1(self):
root = TreeNode(5)
node1 = TreeNode(3)
node2 = TreeNode(6)
node3 = TreeNode(2)
node4 = TreeNode(4)
node5 = TreeNode(1)

root.left = node1
root.right = node2
node1.left = node3
node1.right = node4
node3.left = node5

p = node5
q = node1
output = root
self.assertEqual(Solution().lowestCommonAncestor(root, p, q), output)

def test_2(self):
root = TreeNode(2)
node1 = TreeNode(1)

root.left = node1

p = TreeNode(2)
q = TreeNode(1)
output = root
self.assertEqual(Solution().lowestCommonAncestor(root, p, q), output)


if __name__ == '__main__':
main()
66 changes: 66 additions & 0 deletions meeting-rooms/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import List
from unittest import TestCase, main


# Definition of Interval:
class Interval(object):
def __init__(self, start, end):
self.start = start
self.end = end


class Solution:
def canAttendMeetings(self, intervals: List[Interval]) -> bool:
return self.solve_sort_stack(intervals)

"""
LintCode 로그인이 안되어서 hhttps://neetcode.io/problems/meeting-schedule 에서 실행시키고 통과만 확인했습니다.

Runtime: ? ms (Beats ?%)
Time Complexity: O(n log n)
- intervals 정렬에 O(n log n)
- intervals 조회하며 stack의 마지막 값의 end와 현재 interval의 start를 비교하는데 O(n)
> O(n log n) + O(n) ~= O(n log n)

Memory: ? MB (Beats ?%)
Space Complexity: O(n)
- intervals 메모리를 그대로 사용하면서 정렬했으므로 O(1)
- stack의 크기는 최대 intervals와 같아질 수 있으므로 O(n)
"""

def solve_sort_stack(self, intervals: List[Interval]) -> bool:
intervals.sort(key=lambda x: x.start)

stack: List[Interval] = []
for interval in intervals:
if not stack:
stack.append(interval)
continue

if stack[-1].end > interval.start:
return False
else:
stack.append(interval)

return True


class _LeetCodeTestCases(TestCase):
def test_1(self):
intervals = [Interval(0,30), Interval(5,10), Interval(15,20)]
output = False
self.assertEqual(Solution().canAttendMeetings(intervals), output)

def test_2(self):
intervals = [Interval(5, 8), Interval(9,15)]
output = False
self.assertEqual(Solution().canAttendMeetings(intervals), output)

def test_3(self):
intervals = [Interval(0, 1), Interval(1, 2)]
output = False
self.assertEqual(Solution().canAttendMeetings(intervals), output)


if __name__ == '__main__':
main()
45 changes: 45 additions & 0 deletions non-overlapping-intervals/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from typing import List
from unittest import TestCase, main


class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
return self.solve_stack(intervals)

"""
LintCode 로그인이 안되어서 hhttps://neetcode.io/problems/meeting-schedule 에서 실행시키고 통과만 확인했습니다.

Runtime: 66 ms (Beats 85.10%)
Time Complexity: O(n log n)
- intervals를 정렬하는데 O(n log n)
- intervals를 조회하며 result stack을 갱신하는데 O(n)
- 2항에 대한 or 연산 및 append 메서드만 쓰므로 * O(1)
> O(n log n) + O(n) ~= O(n log n)
Memory: 50.98 MB (Beats 74.30%)
Space Complexity: O(n)
- intervals 정렬도 기존 intervals를 사용하므로 O(1)
- result의 크기가 최대 interval과 같아질 수 있으므로 O(n)
> O(1) + O(n) ~= O(n)
"""
def solve_stack(self, intervals: List[List[int]]) -> int:
if not intervals:
return 0

intervals.sort(key=lambda x: x[1])
result = []
for interval in intervals:
if not result or interval[0] >= result[-1][1]:
result.append(interval)

return len(intervals) - len(result)


class _LeetCodeTestCases(TestCase):
def test_1(self):
intervals = [[1,2],[2,3],[3,4],[1,3]]
output = 1
self.assertEqual(Solution().eraseOverlapIntervals(intervals), output)


if __name__ == '__main__':
main()