diff --git a/merge-intervals/haklee.py b/merge-intervals/haklee.py new file mode 100644 index 000000000..df134ef30 --- /dev/null +++ b/merge-intervals/haklee.py @@ -0,0 +1,130 @@ +"""TC: O(l * i + n), SC: O(n) + +※ 쉬운 길을 돌아가는 풀이라는 것을 감안하고 읽어야 한다!!! +※ 구간의 끝 값이 10^4라고 되어있는 것을 보고 효율 안 따지고 냅다 아래의 방법으로 접근해보았다. + +n은 전체 구간의 끝 값. 문제에서는 10^4으로 주어져 있다. (구체적으로는, 구간의 시작과 끝이 [0, 10^4] 구간에 존재) +i는 전체 인터벌의 개수. 문제에서는 10^4으로 주어져 있다. +l은 각 인터벌의 크기. 문제에서는 10^4으로 주어져 있다. + +아이디어: +- n칸 짜리 일직선으로 되어있는 벽이 있다고 하자. 이 벽에는 아무런 칠이 되어있지 않다. + - [0, 0, ..., 0] 리스트라고 생각하자. +- 모든 인터벌을 순회하면서 인터벌의 시작, 끝 값을 활용하여 이 벽의 일정 구간에 페인트를 칠한다고 하자. + - 특정 구간의 값을 1로 바꾼다. + - [0, ..., 0, 1, ..., 1, 0, ..., 0] + ^ ^ + s e - 1 +- 페인트 칠이 끝났으면 벽의 시작부터 끝까지 훑으면서 칠해진 구간을 찾아내어 결과로 리턴한다. + +SC: +- 벽을 길이 n짜리 리스트로 관리. O(n). +- 페인트 칠이 완료된 벽에서 구간을 찾을때 구간의 시작, 끝 인덱스를 관리하는 값에서 O(1). +- 종합하면 O(n). + +TC: +- 각 인터벌을 순회할 때마다 벽 리스트에서 최대 l개의 아이템에 접근해서 값을 1로 바꾼다. O(l). +- 위의 작업을 인터벌 개수 만큼 진행. 여기까지 O(l * i). +- 페인트 칠이 끝난 벽을 순회하며 인터벌 추출. O(n). +- 종합하면 O(l * i + n). +""" + + +class Solution: + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + max_v = int(1e4 + 2) + flags = [False] * max_v + + for i in intervals: + for v in range(i[0], i[1] + 1): + flags[v] = True + + res = [] + + make_interval = False + int_s, int_e = -1, -1 + + for i in range(max_v): + if flags[i]: + # 인터벌에 포함되어야 하는 값 + if not make_interval: + # 인터벌의 시작 값이다. 인터벌 시작을 i로 세팅. + make_interval = True + int_s = i + else: + # 인터벌에 포함된 값이다. 인터벌 끝을 i로 세팅. + int_e = i + else: + # 인터벌에 포함 안되는 값 + if make_interval: + # 직전 값까지는 인터벌에 포함되었으므로, i에서 + # 인터벌이 끝났다. 인터벌을 더해줌. + res.append([int_s, int_e]) + make_interval = False + + return res + + +""" +그런데 위의 코드를 제출하면 오답이라고 나온다. 왜냐하면, 문제 조건상 인터벌의 s와 e값이 같을 수 있고, +따라서 길이 0짜리 구간이 존재할 수 있기 때문!!!!! (길이 0짜리 인터벌이라니... 분노를 금할 수 없다.) +그래서 위에서 벽에 페인트를 칠하는 비유로는 설명이 불가능한 이상한 인터벌도 결과에 포함시켜서 리턴해야 한다. +이를 위해서 별도의 처리를 한 것이 아래의 코드다. 추가된 코드를 보기 편하게 하기 위해 한글 변수명을 활용했다. +""" + + +class Solution: + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + max_v = int(1e4 + 2) + flags = [False] * max_v + 억까 = [] + + for i in intervals: + for v in range(i[0], i[1]): + flags[v] = True + if i[0] == i[1]: + 억까.append(i[0]) + + 억까 = list(set(억까)) + 억까.sort() + + res = [] + + make_interval = False + int_s, int_e = -2, -2 + 억까_ind = 0 + + for i in range(max_v): + if flags[i]: + # 인터벌에 포함되어야 하는 값 + if not make_interval: + # 길이가 있는 인터벌 앞에 오는 길이 0짜리 인터벌들을 추가해주자. + while 억까_ind < len(억까) and (v := 억까[억까_ind]) <= i: + if int_e + 1 < v: + # 직전에 결과에 추가한 인터벌의 끝 값보다는 커야 한다. + if v < i: + res.append([v, v]) + 억까_ind += 1 + + # 인터벌의 시작 값이다. 인터벌의 시작과 끝을 i로 세팅. + make_interval = True + int_s = int_e = i + + else: + # 인터벌에 포함된 값이다. 인터벌 끝을 i로 세팅. + int_e = i + else: + # 인터벌에 포함 안되는 값 + if make_interval: + # 직전 값까지는 인터벌에 포함되었으므로, i에서 + # 인터벌이 끝났다. 인터벌을 더해줌. + res.append([int_s, int_e + 1]) + make_interval = False + + while 억까_ind < len(억까): + # 끝에 오는 길이 0짜리 인터벌들을 마저 추가해주자. + if int_e + 1 < (v := 억까[억까_ind]): + res.append([v, v]) + 억까_ind += 1 + + return res diff --git a/number-of-connected-components-in-an-undirected-graph/haklee.py b/number-of-connected-components-in-an-undirected-graph/haklee.py new file mode 100644 index 000000000..e74c0f40b --- /dev/null +++ b/number-of-connected-components-in-an-undirected-graph/haklee.py @@ -0,0 +1,63 @@ +"""TC: O(n), SC: O(1) + +n은 주어진 노드의 개수, e는 주어진 엣지의 개수. + +아이디어: +- union-find를 활용하여 disjoint set을 찾는다. + +SC: +- parent, rank값 관리에 각각 길이 n짜리 리스트가 필요하다. O(n). +- 결과 값을 찾을때 각 인덱스마다 find 함수의 결과를 찾아서 리스트로 만들고, 이를 set으로 만들어서 + 길이 측정. 여기서도 O(n). +- 종합하면 O(n). + +TC: +- union, find 각각 union by rank 적용시 O(α(n)) 만큼의 시간이 든다. 이때 α(n)은 inverse Ackermann function + 으로, 매우 느린 속도로 늘어나므로 사실상 상수라고 봐도 무방하다. +- union을 e회, find를 n회 시행하므로 O((n + e) * α(n)). +- 모든 노드에 find를 시행해서 얻은 값을 set으로 만들때 리스트를 전부 순회하므로 O(n). +- 종합하면 O((n + e) * α(n)). +""" + + +class Solution: + """ + @param n: the number of vertices + @param edges: the edges of undirected graph + @return: the number of connected components + """ + + def count_components(self, n: int, edges: List[List[int]]) -> int: + # write your code here + + # union find + parent = list(range(n)) + rank = [0] * n + + def find(x: int) -> bool: + if x == parent[x]: + return x + + parent[x] = find(parent[x]) # path-compression + return parent[x] + + def union(a: int, b: int) -> bool: + pa = find(a) + pb = find(b) + + # union by rank + if pa == pb: + return + + if rank[pa] < rank[pb]: + pa, pb = pb, pa + + parent[pb] = pa + + if rank[pa] == rank[pb]: + rank[pa] += 1 + + for e in edges: + union(*e) + + return len(set(find(i) for i in range(n))) diff --git a/remove-nth-node-from-end-of-list/haklee.py b/remove-nth-node-from-end-of-list/haklee.py new file mode 100644 index 000000000..8f58fc775 --- /dev/null +++ b/remove-nth-node-from-end-of-list/haklee.py @@ -0,0 +1,45 @@ +"""TC: O(n), SC: O(1) + +n은 주어진 리스트의 길이. + +아이디어: +- 길이를 먼저 측정한다. +- 그 다음 제거할 노드의 인덱스를 구해서 해당 인덱스의 아이템을 제거. + +SC: +- 리스트의 길이 값 및 리스트 탐색에 사용하는 인덱스 값을 관리. O(1). + +TC: +- 길이 값 구할때 리스트를 전체 순회. O(n). +- 특정 인덱스에 해당하는 노드 제거시 최악의 경우 끝 노드까지 탐색해야 한다. O(n). +- 종합하면 O(n). +""" + + +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + # 길이 측정 + l = 0 + cur_head = head + while True: + if not cur_head: + break + l += 1 + cur_head = cur_head.next + + # 노드 하나 제거 + i = 0 + dummy_head = ListNode() + dummy_head.next = head + cur_head = dummy_head + while i != l - n: + i += 1 + cur_head = cur_head.next + + cur_head.next = None if cur_head.next is None else cur_head.next.next + return dummy_head.next diff --git a/same-tree/haklee.py b/same-tree/haklee.py new file mode 100644 index 000000000..824f6df8c --- /dev/null +++ b/same-tree/haklee.py @@ -0,0 +1,35 @@ +"""TC: O(n), SC: O(h) + +n은 주어진 트리 p, q의 노드 개수 중 더 작은 쪽의 값. +h는 주어진 트리 p의 높이. + +아이디어: +- p를 dfs로 돌면서 q도 같은 순서로 dfs를 돌린다. +- 이때 순회하다가 하나라도 다른 값이 나오면 False. 모두 같으면 True. + +SC: +- p를 기준으로 dfs를 돌고 있으므로 호출 스택의 깊이가 p의 깊이보다 깊어질 수 없다. O(h). + +TC: +- 최악의 경우 모든 노드 순회 후 True 리턴. O(n). +- False를 리턴할 경우 트리 순회 중 멈춘다. 이 경우 두 트리 중 더 적은 노드 개수 보다 + 적은 회수 만큼 순회. 이 경우에도 O(n). +- 종합하면 O(n). +""" + + +# 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 (p is None and q is None) or ( + p is not None + and q is not None + and p.val == q.val + and self.isSameTree(p.left, q.left) + and self.isSameTree(p.right, q.right) + ) diff --git a/serialize-and-deserialize-binary-tree/haklee.py b/serialize-and-deserialize-binary-tree/haklee.py new file mode 100644 index 000000000..4ce871eb1 --- /dev/null +++ b/serialize-and-deserialize-binary-tree/haklee.py @@ -0,0 +1,74 @@ +"""TC: O(n), SC: O(1) + +n은 주어진 트리의 노드 개수. + +아이디어: +- 트리 구조를 dict로 만들어버리자. + - Node = None | {v: int, l: Node, r: Node} +- 이 dict를 python에 있는 json 패키지를 써서 string으로 바꾸고, string에서 불러온다. + +SC: +- dict에 들어가는 정보의 크기는 노드 개수만큼 커지며, 이걸 그대로 string으로 바꾸기 때문에 + 노드 개수에 비례하여 증가. O(n). + +TC: +- serialize, deserialize 과정 모두 노드 개수만큼 순회. O(n). +""" + +# Definition for a binary tree node. +# class TreeNode(object): +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +import json + + +class Codec: + + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + + def write_node(node): + d = None + if node: + d = { + "v": node.val, + "l": write_node(node.left), + "r": write_node(node.right), + } + return d + + return json.dumps(write_node(root)) + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + + data = json.loads(data) + + def read_data(d): + if d is None: + return None + + node = TreeNode(d["v"]) + node.left = read_data(d["l"]) + node.right = read_data(d["r"]) + + return node + + return read_data(data) + + +# Your Codec object will be instantiated and called as such: +# ser = Codec() +# deser = Codec() +# ans = deser.deserialize(ser.serialize(root))