diff --git a/graph-valid-tree/dusunax.py b/graph-valid-tree/dusunax.py new file mode 100644 index 000000000..7f4581c61 --- /dev/null +++ b/graph-valid-tree/dusunax.py @@ -0,0 +1,67 @@ +''' +# 261. Graph Valid Tree + +## What constitutes a 🌲 +1. it's a graph. +2. Connected: edges == n - 1, visited node count == n +3. Acyclic: there is no cycle. + +## Approach A. DFS +use DFS to check if there is a cycle in the graph. +- if there were no cycle & visited node count == n, return True. + +## Approach B. Disjoint Set Union (서로소 집합) +use Disjoint Set Union to check if there is a cycle in the graph. +- if you find a cycle, return False immediately. +- if you find no cycle, return True. + +### Union Find Operation +- Find: find the root of a node. + - if the root of two nodes is already the same, there is a cycle. +- Union: connect two nodes. + +## Approach Comparison +- **A. DFS**: simple and easy to understand. +- **B. Disjoint Set Union**: quicker to check if there is a cycle. if there were more edges, Union Find would be faster. +''' +class Solution: + def validTreeDFS(self, n: int, edges: List[List[int]]) -> bool: + if len(edges) != n - 1: + return False + + graph = [[] for _ in range(n)] + for node, neighbor in edges: + graph[node].append(neighbor) + graph[neighbor].append(node) + + visited = set() + def dfs(node): + visited.add(node) + for neighbor in graph[node]: + if neighbor not in visited: + dfs(neighbor) + + dfs(0) + return len(visited) == n + + def validTreeUnionFind(self, n: int, edges: List[List[int]]) -> bool: + if len(edges) != n - 1: + return False + + parent = [i for i in range(n)] + + def find(x): + if x == parent[x]: + return x + parent[x] = find(parent[x]) + return parent[x] + + def union(x, y): + parent[find(x)] = find(y) + + for node, neighbor in edges: + if find(node) == find(neighbor): + return False + union(node, neighbor) + + return True diff --git a/maximum-depth-of-binary-tree/dusunax.py b/maximum-depth-of-binary-tree/dusunax.py new file mode 100644 index 000000000..e1c80a2a4 --- /dev/null +++ b/maximum-depth-of-binary-tree/dusunax.py @@ -0,0 +1,61 @@ +''' +# 104. Maximum Depth of Binary Tree + +use DFS to find the maximum depth of the binary tree. + +## A. if we use a helper function (not a good idea) +``` +helper function explanation: +- store the max_depth in the nonlocal variable.(outside of the helper function) +- base case: if the node is None, update the max_depth and return. +- in the helper function, do recursive call for the left and right children of the node. + - update the count for the depth of the tree. +- update the max_depth when the node is a leaf node's children. +``` + +## B. return the max_depth directly +👉 why helper function is not necessary? +recursion function can return the max_depth directly. +remove side effect & non-local variable. + +## TC is O(n) + +visit each node once for checking if it is a leaf node's children. + +## SC is O(h) + +h for height of the tree +''' +class Solution: + ''' + A. first approach with side effect + ''' + def maxDepthWithHelper(self, root: Optional[TreeNode]) -> int: + max_depth = 0 + + def helper(node, count): + nonlocal max_depth + if node is None: + max_depth = max(max_depth, count) + return + + helper(node.left, count+1) + helper(node.right, count + 1) + + helper(root, max_depth) + + return max_depth + + ''' + B. return the max_depth directly. + - more concise & readable + - no side effect & non-local variable + ''' + def maxDepth(self, root: Optional[TreeNode]) -> int: + if root is None: + return 0 + + left_count = self.maxDepth(root.left) + right_count = self.maxDepth(root.right) + + return max(left_count, right_count) + 1 diff --git a/reorder-list/dusunax.py b/reorder-list/dusunax.py new file mode 100644 index 000000000..e372d2a38 --- /dev/null +++ b/reorder-list/dusunax.py @@ -0,0 +1,49 @@ +''' +# 143. Reorder list +use two pointers for each steps. + +1. finding the middle + - two pointers: slow, fast + - move slow 1, fast 2 until fast reaches the end. +2. reversing the second half + - two pointers: prev, curr + - start from slow to end, do common reverse linked list operation. + - need to break the links to halves beforehand. +3. merging first & second + - two pointers: frist, second + - merge second between first, until second is None + +## TC is O(n) +- find the middle: O(n) +- reverse the second half: O(n) +- merge the two halves: O(n) + +## SC is O(1) +- no extra space is used. +''' +class Solution: + def reorderList(self, head: Optional[ListNode]) -> None: + # 1. finding the middle + slow, fast = head, head + while fast and fast.next: + slow = slow.next + fast = fast.next.next + + # 2. reversing second half + second_not_reversed = slow.next + slow.next = None + prev, curr = None, second_not_reversed + while curr: + temp = curr.next + curr.next = prev + prev = curr + curr = temp + + # 3. merging first & second + first, second = head, prev + while second: + temp1, temp2 = first.next, second.next + first.next = second + second.next = temp1 + first = temp1 + second = temp2