diff --git a/non-overlapping-intervals/seungriyou.py b/non-overlapping-intervals/seungriyou.py new file mode 100644 index 000000000..20ad7aff2 --- /dev/null +++ b/non-overlapping-intervals/seungriyou.py @@ -0,0 +1,57 @@ +# https://leetcode.com/problems/non-overlapping-intervals/ + +from typing import List + +class Solution: + def eraseOverlapIntervals1(self, intervals: List[List[int]]) -> int: + """ + [Complexity] + - TC: O(nlogn) (sort) + - SC: O(1) (tim sort -> 최악의 경우 O(n)까지 가능) + + [Approach] + intervals를 start 기준으로 오름차순 정렬 후, greedy 하게 항상 작은 end를 가지는 interval을 선택한다. + """ + # sort asc by start + intervals.sort(key=lambda x: x[0]) + + # initialize prev_e + prev_e = intervals[0][0] - 1 + to_be_removed = 0 + + for s, e in intervals: + # (1) overlapping : greedy하게 prev_e와 e 중 더 작은 값으로 prev_e를 업데이트하고, 제거할 interval 수 증가 + if prev_e > s: + prev_e = min(prev_e, e) + to_be_removed += 1 + # (2) non-overlapping : prev_e만 업데이트 + else: + prev_e = e + + return to_be_removed + + def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: + """ + [Complexity] + - TC: O(nlogn) (sort) + - SC: O(1) (tim sort -> 최악의 경우 O(n)까지 가능) + + [Approach] + 이전 풀이의 (1) overlapping 단계에서 prev_e와 e 중 min을 고르는 로직을 제거하려면, intervals를 end 기준으로 오름차순 정렬하면 된다. + """ + # sort asc by end + intervals.sort(key=lambda x: x[1]) + + # initialize prev_e + prev_e = intervals[0][0] - 1 + to_be_removed = 0 + + for s, e in intervals: + # (1) overlapping : greedy하게 prev_e와 e 중 더 작은 값으로 선택하면 되므로 prev_e를 그대로 두고, 제거할 interval 수 증가 + if prev_e > s: + to_be_removed += 1 + # (2) non-overlapping : prev_e만 업데이트 + else: + prev_e = e + + return to_be_removed diff --git a/number-of-connected-components-in-an-undirected-graph/seungriyou.py b/number-of-connected-components-in-an-undirected-graph/seungriyou.py new file mode 100644 index 000000000..71b32c323 --- /dev/null +++ b/number-of-connected-components-in-an-undirected-graph/seungriyou.py @@ -0,0 +1,108 @@ +# https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/ + +from typing import List + +class Solution: + def countComponents_bfs(self, n: int, edges: List[List[int]]) -> int: + """ + [Complexity] + - TC: O(v + e) (모든 edge & node 한 번씩 탐색) + - SC: O(v + e) (graph) + + [Approach] + BFS로 visited를 기록해가며 connected component를 센다. + """ + from collections import deque + + graph = [[] for _ in range(n)] + for a, b in edges: + graph[a].append(b) + graph[b].append(a) + + visited, res = set(), 0 + + def bfs(start): + q = deque([start]) + visited.add(start) + + while q: + pos = q.popleft() + for npos in graph[pos]: + if npos not in visited: + q.append(npos) + visited.add(npos) + + return + + for i in range(n): + if i not in visited: + bfs(i) + res += 1 + + return res + + def countComponents_dfs(self, n: int, edges: List[List[int]]) -> int: + """ + [Complexity] + - TC: O(v + e) (모든 edge & node 한 번씩 탐색) + - SC: O(v + e) (graph) + + [Approach] + DFS로 visited를 기록해가며 connected component를 센다. + """ + graph = [[] for _ in range(n)] + for a, b in edges: + graph[a].append(b) + graph[b].append(a) + + visited, res = set(), 0 + + def dfs(pos): + # 이전에 visited 포함 여부 확인하므로 base condition 생략 가능 + + visited.add(pos) + + # recur + for npos in graph[pos]: + if npos not in visited: + dfs(npos) + + return + + for i in range(n): + if i not in visited: + dfs(i) + res += 1 + + return res + + def countComponents(self, n: int, edges: List[List[int]]) -> int: + """ + [Complexity] + - TC: O(v + e * α(v)) (모든 edge & node 한 번씩 탐색) + - SC: O(v) (parent, set(...)) + + [Approach] + edges를 iterate 하며 union-find 수행 후, parent의 종류의 개수를 세면 된다. + parent의 종류의 개수를 셀 때는 다시 find_parent(x)로 찾아야 한다! + """ + + def find_parent(x): + if x != parent[x]: + parent[x] = find_parent(parent[x]) + return parent[x] + + def union_parent(x, y): + px, py = find_parent(x), find_parent(y) + + if px < py: + parent[py] = px + else: + parent[px] = py + + parent = [i for i in range(n)] + + for x, y in edges: + union_parent(x, y) + + return len(set(find_parent(i) for i in range(n))) diff --git a/remove-nth-node-from-end-of-list/seungriyou.py b/remove-nth-node-from-end-of-list/seungriyou.py new file mode 100644 index 000000000..1007d23c8 --- /dev/null +++ b/remove-nth-node-from-end-of-list/seungriyou.py @@ -0,0 +1,133 @@ +# https://leetcode.com/problems/remove-nth-node-from-end-of-list/ + +from typing import Optional + +# Definition for singly-linked list. +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class Solution: + def removeNthFromEnd_recur(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + """ + [Complexity] + - TC: O(len) + - SC: O(len) (call stack) + + [Approach] + 재귀적으로 linked list를 확인하며 끝에서부터의 순서를 확인 후, 다음 node의 순서가 n과 같을 때 다음 node를 건너뛰도록 한다. + head node를 제거해야 하는 경우(= 끝에서부터 n번째인 node가 head인 경우)에는, 재귀 함수 내에서 node 제거가 불가능하므로 head.next를 반환한다. + """ + + def check_order_from_end(curr): + # base condition + if not curr.next: + return 1 + + # recur + next_order_from_end = check_order_from_end(curr.next) + # nth from end인 node를 건너뛰기 + if next_order_from_end == n: + curr.next = curr.next.next + + return next_order_from_end + 1 + + # head node를 제거해야 하는 경우라면, check_order_from_end() 내에서 node 제거가 불가능하므로 head.next를 반환해야 함 + if check_order_from_end(head) == n: + return head.next + + return head + + def removeNthFromEnd_recur2(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + """ + [Complexity] + - TC: O(len) + - SC: O(len) (call stack) + + [Approach] + 이전 풀이에서 head node를 제거해야 하는 경우를 따로 처리하지 않으려면, 주어진 head를 가리키는 dummy node(= prev)를 추가하고 + dummy node의 next node를 반환하면 된다. + """ + + def check_order_from_end(curr): + # base condition + if not curr.next: + return 1 + + # recur + next_order_from_end = check_order_from_end(curr.next) + # nth from end인 node를 건너뛰기 + if next_order_from_end == n: + curr.next = curr.next.next + + return next_order_from_end + 1 + + prev = ListNode(next=head) + check_order_from_end(prev) + + return prev.next + + def removeNthFromEnd_length(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + """ + [Complexity] + - TC: O(n) + - SC: O(1) + + [Approach] + linked list의 전체 길이를 구하고, head에서부터 (길이 - n - 1) 번 전진하여 node를 건너뛰면 된다. + (2 pass) + """ + # linked list의 length 구하기 + length = 0 + curr = head + while curr: + length += 1 + curr = curr.next + + # length == n라면, head를 제거 + if length == n: + return head.next + + # length - n - 1 번 이동 + curr = head + for _ in range(length - n - 1): + curr = curr.next + + # node 제거 + curr.next = curr.next.next + + return head + + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + """ + [Complexity] + - TC: O(len) + - SC: O(1) + + [Approach] + slow, fast의 two pointer를 이용해 반복문으로 풀 수 있다. (1 pass) + 1. fast를 n 번 전진 + 2. fast가 끝에 도달한 경우, 첫 번째 node를 제거해야하므로 head.next 반환 + 3. 현재 fast의 위치에서 slow와 fast를 함께 전진하면, fast가 끝에 도달할 때 slow는 뒤에서부터 n + 1번째 node임 + 4. 뒤에서부터 n + 1번째인 node가 n - 1번째 node를 가리키도록 함 + """ + slow = fast = head + + # 1. fast를 n 번 전진 + for _ in range(n): + fast = fast.next + + # 2. fast가 끝에 도달한 경우, 첫 번째 node를 제거해야하므로 head.next 반환 + if not fast: + return head.next + + # 3. 현재 fast의 위치에서 slow와 fast를 함께 전진하면, + # fast가 끝에 도달할 때 slow는 뒤에서부터 n + 1번째 node임 + while fast.next: + slow, fast = slow.next, fast.next + + # 4. 뒤에서부터 n + 1번째인 node가 n - 1번째 node를 가리키도록 함 + slow.next = slow.next.next + + return head diff --git a/same-tree/seungriyou.py b/same-tree/seungriyou.py new file mode 100644 index 000000000..ff098a8c6 --- /dev/null +++ b/same-tree/seungriyou.py @@ -0,0 +1,34 @@ +# https://leetcode.com/problems/same-tree/ + +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 isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool: + """ + [Complexity] + - TC: O(n) + - SC: O(height) (call stack) + + [Approach] + 재귀적으로 두 tree를 타고 내려가며 확인할 수 있다. + 각 단계에서 두 tree가 다르다고 판단할 수 있는 조건은 + (1) 한 쪽 node만 None이거나 + (2) 두 node의 값이 다른 + 경우이다. + """ + # base condition + if not p and not q: + return True + + # not same + if not p or not q or p.val != q.val: + return False + + return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) diff --git a/serialize-and-deserialize-binary-tree/seungriyou.py b/serialize-and-deserialize-binary-tree/seungriyou.py new file mode 100644 index 000000000..4ff5c51d0 --- /dev/null +++ b/serialize-and-deserialize-binary-tree/seungriyou.py @@ -0,0 +1,64 @@ +# https://leetcode.com/problems/serialize-and-deserialize-binary-tree/ + +# Definition for a binary tree node. +class TreeNode(object): + def __init__(self, x): + self.val = x + self.left = None + self.right = None + +class Codec: + + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + res = [] + + def preorder_ser(node): + # base condition + if not node: + res.append("None") + return + + # traversal + res.append(str(node.val)) + preorder_ser(node.left) + preorder_ser(node.right) + + preorder_ser(root) + + return ",".join(res) + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + from collections import deque + + preorder_vals = deque(data.split(",")) + + def preorder_deser(): + val = preorder_vals.popleft() + + # base condition + if val == "None": + return None + + # traversal + node = TreeNode(int(val)) + node.left = preorder_deser() + node.right = preorder_deser() + + return node + + return preorder_deser() + +# Your Codec object will be instantiated and called as such: +# ser = Codec() +# deser = Codec() +# ans = deser.deserialize(ser.serialize(root))