From 6db246c97466586338d0ec98cebcf772ca6eb05f Mon Sep 17 00:00:00 2001 From: yolophg Date: Thu, 20 Feb 2025 09:58:05 -0500 Subject: [PATCH] solve: binaryTreeMaximumPathSum, graphValidTree, maximumDepthOfBinaryTree, mergeIntervals and reorderList --- binary-tree-maximum-path-sum/yolophg.py | 26 +++++++++++++++++++ graph-valid-tree/yolophg.py | 30 ++++++++++++++++++++++ maximum-depth-of-binary-tree/yolophg.py | 14 ++++++++++ merge-intervals/yolophg.py | 27 ++++++++++++++++++++ reorder-list/yolophg.py | 34 +++++++++++++++++++++++++ 5 files changed, 131 insertions(+) create mode 100644 binary-tree-maximum-path-sum/yolophg.py create mode 100644 graph-valid-tree/yolophg.py create mode 100644 maximum-depth-of-binary-tree/yolophg.py create mode 100644 merge-intervals/yolophg.py create mode 100644 reorder-list/yolophg.py diff --git a/binary-tree-maximum-path-sum/yolophg.py b/binary-tree-maximum-path-sum/yolophg.py new file mode 100644 index 000000000..b67a552a7 --- /dev/null +++ b/binary-tree-maximum-path-sum/yolophg.py @@ -0,0 +1,26 @@ +# Time Complexity: O(N) - visit each node once. +# Space Complexity: O(H) - recursive call stack, where H is the tree height. +# - O(log N) for balanced trees, O(N) for skewed trees. + +class Solution: + def maxPathSum(self, root: Optional[TreeNode]) -> int: + def dfs(node): + nonlocal ans + if not node: + return 0 + + # get the max path sum from left and right subtrees (ignore negatives) + left = max(dfs(node.left), 0) + right = max(dfs(node.right), 0) + + # update the global max path sum including this node + ans = max(ans, left + right + node.val) + + # return the max path sum that can be extended to the parent + return max(left, right) + node.val + + # initialize with the smallest possible value + ans = float('-inf') + # start DFS from the root + dfs(root) + return ans diff --git a/graph-valid-tree/yolophg.py b/graph-valid-tree/yolophg.py new file mode 100644 index 000000000..2c4187989 --- /dev/null +++ b/graph-valid-tree/yolophg.py @@ -0,0 +1,30 @@ +# Time Complexity: O(N) - iterate through all edges and nodes at most once. +# Space Complexity: O(N) - store the graph as an adjacency list and track visited nodes. + +class Solution: + def valid_tree(self, n: int, edges: List[List[int]]) -> bool: + # a tree with 'n' nodes must have exactly 'n-1' edges + if len(edges) != n - 1: + return False + + # build an adjacency list (graph representation) + graph = {i: [] for i in range(n)} + for u, v in edges: + graph[u].append(v) + graph[v].append(u) + + # use BFS to check if the graph is fully connected and acyclic + visited = set() + queue = [0] + visited.add(0) + + while queue: + node = queue.pop(0) + for neighbor in graph[node]: + if neighbor in visited: + continue + visited.add(neighbor) + queue.append(neighbor) + + # if we visited all nodes, it's a valid tree + return len(visited) == n diff --git a/maximum-depth-of-binary-tree/yolophg.py b/maximum-depth-of-binary-tree/yolophg.py new file mode 100644 index 000000000..487c228a7 --- /dev/null +++ b/maximum-depth-of-binary-tree/yolophg.py @@ -0,0 +1,14 @@ +# Time Complexity: O(N) - visit each node once. +# Space Complexity: O(H) - the recursion stack goes as deep as the height of the tree. +# - Worst case (skewed tree): O(N) +# - Best case (balanced tree): O(log N) + +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + # if there's no node, the depth is just 0. + if not root: + return 0 + + # recursively get the depth of left and right subtrees + # then add 1 for the current node + return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right)) diff --git a/merge-intervals/yolophg.py b/merge-intervals/yolophg.py new file mode 100644 index 000000000..cb057923c --- /dev/null +++ b/merge-intervals/yolophg.py @@ -0,0 +1,27 @@ +# Time Complexity: O(N log N) - sorting takes O(N log N), and merging takes O(N) +# Space Complexity: O(N) - store the merged intervals in a new list. + +class Solution: + + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + # sort the intervals by their start values + intervals.sort(key=lambda x: x[0]) + + # this will store our final merged intervals + merged = [] + # start with the first interval + prev = intervals[0] + + # iterate through the sorted intervals + for interval in intervals[1:]: + if interval[0] <= prev[1]: + # overlapping intervals → merge them by updating 'end' value + prev[1] = max(prev[1], interval[1]) + else: + # no overlap → add 'prev' to the merged list and update 'prev' + merged.append(prev) + prev = interval + + merged.append(prev) + + return merged diff --git a/reorder-list/yolophg.py b/reorder-list/yolophg.py new file mode 100644 index 000000000..dc5c17460 --- /dev/null +++ b/reorder-list/yolophg.py @@ -0,0 +1,34 @@ +# Time Complexity: O(N) - traverse the list three times: +# (1) find the middle (O(N)), +# (2) reverse the second half (O(N)), +# (3) merge the two halves (O(N)). +# Space Complexity: O(1) - use a few extra pointers, no additional data structures. + +class Solution: + def reorderList(self, head: Optional[ListNode]) -> None: + # find the middle of the list using slow & fast pointers + slow, fast = head, head.next + while fast and fast.next: + slow = slow.next + fast = fast.next.next + + # reverse the second half of the list + second = slow.next + # cut the list into two halves + slow.next = None + prev = None + + while second: + temp = second.next + second.next = prev + prev = second + second = temp + + # merge the two halves (alternating nodes) + first, second = head, prev + + while second: + temp1, temp2 = first.next, second.next + first.next = second + second.next = temp1 + first, second = temp1, temp2