Skip to content

[EGON] Week12 Solutions #569

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
Nov 2, 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
57 changes: 57 additions & 0 deletions merge-intervals/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from typing import List
from unittest import TestCase, main


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

"""
Runtime: 8 ms (Beats 49.77%)
Time Complexity:
- intervals의 길이를 n이라 하면, intervals를 시작 지점을 기준으로 정렬하는데 O(n log n)
- intervals를 조회하면서 연산하는데, 내부 연산은 모두 O(1)이므로 O(n)
> O(n log n) + O(n) ~= O(n log n)

Memory: 19.88 MB (Beats: 99.31%)
Space Complexity: O(n)
- intervals는 내부 정렬만 했으므로 추가 메모리 사용 메모리 없음
- 겹치는 구간이 없는 최악의 경우 merged의 크기는 intervals의 크기와 같아지므로, O(n) upper bound
"""

def solve_stack(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort()

merged = []
for interval in intervals:
if not merged:
merged.append(interval)
continue

new_start, new_end = interval
_, last_end = merged[-1]
if last_end < new_start:
merged.append(interval)
else:
merged[-1][1] = max(last_end, new_end)

return merged


class _LeetCodeTestCases(TestCase):

def test_1(self):
intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]
output = [[1, 6], [8, 10], [15, 18]]

self.assertEqual(Solution().merge(intervals), output)

def test_2(self):
intervals = [[1, 4], [4, 5]]
output = [[1, 5]]

self.assertEqual(Solution().merge(intervals), output)


if __name__ == '__main__':
main()
77 changes: 77 additions & 0 deletions number-of-connected-components-in-an-undirected-graph/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from typing import List
from unittest import TestCase, main


class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next


class Solution:
def countComponents(self, n: int, edges: List[List[int]]) -> int:
return self.solve_union_find(n, edges)

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

Runtime: ? ms (Beats ?%)
Time Complexity: O(max(m, n))
- UnionFind의 parent 생성에 O(n)
- edges 조회에 O(m)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맥락상 medges 리스트의 길이라는 것을 알 수 있긴 한데, 그렇더라도 설명 어딘가에 명시되어있다면 읽기 더 편했을것 같습니다.

- Union-find 알고리즘의 union을 매 조회마다 사용하므로, * O(α(n)) (α는 아커만 함수의 역함수)
- UnionFind의 각 노드의 부모를 찾기 위해, n번 find에 O(n * α(n))
> O(n) + O(m * α(n)) + O(n * α(n)) ~= O(max(m, n) * α(n)) ~= O(max(m, n)) (∵ α(n) ~= C)

Memory: ? MB (Beats ?%)
Space Complexity: O(n)
- UnionFind의 parent와 rank가 크기가 n인 리스트이므로, O(n) + O(n)
> O(n) + O(n) ~= O(n)
"""

def solve_union_find(self, n: int, edges: List[List[int]]) -> int:

class UnionFind:
def __init__(self, size: int):
self.parent = [i for i in range(size)]
self.rank = [1] * size

def union(self, first: int, second: int):
first_parent, second_parent = self.find(first), self.find(second)

if self.rank[first_parent] > self.rank[second_parent]:
self.parent[second_parent] = first_parent
elif self.rank[first_parent] < self.rank[second_parent]:
self.parent[first_parent] = second_parent
else:
self.parent[second_parent] = first_parent
self.rank[first_parent] += 1

def find(self, node: int):
if self.parent[node] != node:
self.parent[node] = self.find(self.parent[node])

return self.parent[node]

union_find = UnionFind(size=n)
for first, second in edges:
union_find.union(first, second)

result = set()
for i in range(n):
result.add(union_find.find(i))

return len(result)


class _LeetCodeTestCases(TestCase):
def test_1(self):
n = 5
edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]]
output = False

self.assertEqual(Solution().countComponents(n, edges), output)


if __name__ == '__main__':
main()
91 changes: 91 additions & 0 deletions remove-nth-node-from-end-of-list/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from typing import Optional
from unittest import TestCase, main


# Definition for singly-linked list.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next

def values(self) -> [int]:
result = []
node = self
while node:
result.append(node.val)
node = node.next

return result


class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
return self.solve_two_pointer(head, n)
"""
Runtime: 0 ms (Beats 100.00%)
Time Complexity: O(n)
> list의 모든 node + dummy node를 탐색하므로 O(n + 1) ~= O(n)

Memory: 16.62 MB (Beats 15.78%)
Space Complexity: O(1)
> 포인터만 사용하므로 O(1)
"""
def solve_two_pointer(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
if head.next is None:
return None

dummy = ListNode(-1)
dummy.next = head
head = dummy

slow = fast = head
while n:
if fast.next:
fast = fast.next
n -= 1

while fast is not None:
if fast.next is None:
slow.next = slow.next.next
break
else:
slow = slow.next
fast = fast.next

return head.next


class _LeetCodeTestCases(TestCase):
def test_1(self):
node_1 = ListNode(1)
node_2 = ListNode(2)
node_3 = ListNode(3)
node_4 = ListNode(4)
node_5 = ListNode(5)

node_1.next = node_2
node_2.next = node_3
node_3.next = node_4
node_4.next = node_5

n = 2
output = [1, 2, 3, 5]
self.assertEqual(Solution().removeNthFromEnd(node_1, n).values(), output)

def test_2(self):
node_1 = ListNode(1)
n = 1
output = []
self.assertEqual(Solution().removeNthFromEnd(node_1, n).values(), output)

def test_3(self):
node_1 = ListNode(1)
node_2 = ListNode(2)
node_1.next = node_2
n = 2
output = [2]
self.assertEqual(Solution().removeNthFromEnd(node_1, n).values(), output)


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


# 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:
return self.solve_dfs(p, q)

"""
Runtime: 0 ms (Beats 100.00%)
Time Complexity: O(min(p, q))
> dfs를 통해 모든 node를 방문하므로, 각 트리의 node의 갯수를 각각 p, q라 하면, O(min(p, q))

Memory: 16.62 MB (Beats 15.78%)
Space Complexity: O(min(p, q))
> 일반적인 경우 트리의 깊이만큼 dfs 호출 스택이 쌓이나, 최악의 경우 한쪽으로 편향되었다면 O(min(p, q)), upper bound
"""
def solve_dfs(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:

def dfs(p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p is None and q is None:
return True
elif (p is not None and q is not None) and (p.val == q.val):
return dfs(p.left, q.left) and dfs(p.right, q.right)
else:
return False

return dfs(p, q)


class _LeetCodeTestCases(TestCase):
def test_1(self):
p_1 = TreeNode(1)
p_2 = TreeNode(2)
p_3 = TreeNode(3)
p_1.left = p_2
p_1.right = p_3

q_1 = TreeNode(1)
q_2 = TreeNode(3)
q_3 = TreeNode(3)
q_1.left = q_2
q_1.right = q_3

self.assertEqual(Solution().isSameTree(p_1, q_1), True)


if __name__ == '__main__':
main()
65 changes: 65 additions & 0 deletions serialize-and-deserialize-binary-tree/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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 Codec:
"""
Runtime: 76 ms (Beats 85.72%)
Time Complexity: O(n)
> dfs를 통해 모든 node를 방문하므로, O(n)

Memory: 21.40 MB (Beats 12.82%)
Space Complexity: O(n)
> 일반적인 경우 트리의 깊이만큼 dfs 호출 스택이 쌓이나, 최악의 경우 한쪽으로 편향되었다면 트리의 깊이가 n이 될 수 있으므로 O(n), upper bound
"""

def serialize(self, root):
"""Encodes a tree to a single string.

:type root: TreeNode
:rtype: str
"""

def dfs(node: Optional[TreeNode]) -> None:
if node is None:
result.append("null")
return
result.append(str(node.val))
dfs(node.left)
dfs(node.right)

result = []
dfs(root)
return ','.join(result)

def deserialize(self, data):
"""Decodes your encoded data to tree.

:type data: str
:rtype: TreeNode
"""

def dfs() -> Optional[TreeNode]:
val = next(values)
if val == "null":
return None
node = TreeNode(int(val))
node.left = dfs()
node.right = dfs()
return node

values = iter(data.split(','))
return dfs()


# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))