From 3f687753ae184ac116552a7409e1cfe0f53dca37 Mon Sep 17 00:00:00 2001 From: Sanket Kale Date: Sun, 28 Jun 2026 00:11:42 -0500 Subject: [PATCH] Completed Trees-3 --- Problem1-backtracking.py | 58 ++++++++++++++++++++++++++++++++++++++++ Problem1.py | 52 +++++++++++++++++++++++++++++++++++ Problem2.py | 41 ++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 Problem1-backtracking.py create mode 100644 Problem1.py create mode 100644 Problem2.py diff --git a/Problem1-backtracking.py b/Problem1-backtracking.py new file mode 100644 index 00000000..876906f0 --- /dev/null +++ b/Problem1-backtracking.py @@ -0,0 +1,58 @@ +## Problem1 (https://leetcode.com/problems/path-sum-ii/) +import copy + +class Solution: + # Time Complexity: O(N) + # Why: We visit every node in the tree exactly once during the DFS traversal, + # where N is the total number of nodes. + # (Note: In the worst-case scenario where a large number of valid paths are found, + # copying the path to the result list could push the time closer to O(N^2), + # but the core traversal itself is strictly O(N)). + # + # Space Complexity: O(H) auxiliary space, where H is the height of the tree. + # Why: By utilizing backtracking, we only maintain a single list (`nodesInPath`) + # rather than creating new lists at every step. The memory used is defined by the + # maximum depth of the recursion stack and our path list, which is at most the + # height of the tree. + # - Best/Average Case (Balanced Tree): O(log N) + # - Worst Case (Skewed Tree): O(N) + + def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]: + result = [] + + # Helper function to perform Depth First Search (DFS) with backtracking + def helper(node, currentSum, nodesInPath): + nonlocal targetSum, result + + # Base case: if the current node is null, stop exploring this branch + if node == None: + return + + # Include the current node's value in our running total + currentSum += node.val + + # Add the current node to our path tracking list + nodesInPath.append(node.val) + + # Check if this is a leaf node AND if the running sum matches our target + if node.left == None and node.right == None and currentSum == targetSum: + # Valid path found! We must append a copy of `nodesInPath` to the + # result array. If we didn't copy it, subsequent modifications to + # `nodesInPath` during backtracking would alter the saved result. + result.append(copy.deepcopy(nodesInPath)) + + # Recursively search the left and right subtrees + helper(node.left, currentSum, nodesInPath) + helper(node.right, currentSum, nodesInPath) + + # BACKTRACKING STEP: + # We are done exploring both subtrees for the current node. + # We remove (pop) the current node's value from `nodesInPath` before + # returning up the call stack, so the parent node can explore its + # other branches with a clean slate. + nodesInPath.pop() + + # Kick off the traversal starting at the root + helper(root, 0, []) + + return result \ No newline at end of file diff --git a/Problem1.py b/Problem1.py new file mode 100644 index 00000000..cc8347d2 --- /dev/null +++ b/Problem1.py @@ -0,0 +1,52 @@ +## Problem1 (https://leetcode.com/problems/path-sum-ii/) +# 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 +import copy + +class Solution: + # Time Complexity: O(N^2) in the worst case (completely unbalanced/skewed tree). + # Why: We visit each node once O(N), but at each node, we do a copy.deepcopy() of the path. + # The path length can grow up to N, meaning the copies take 1 + 2 + 3 + ... + N = O(N^2) time. + # (For a perfectly balanced tree, it would be O(N log N)). + # + # Space Complexity: O(N^2) in the worst case for auxiliary space. + # Why: The recursion call stack goes as deep as N. Because we create a deep copy of the + # array at every single step and pass it down, the stack holds references to arrays + # of size 1, 2, 3... N, which takes up O(N^2) memory. + + def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]: + result = [] + + # Helper function to perform Depth First Search (DFS) traversing the tree + def helper(node, currentSum, nodesInPath): + nonlocal targetSum, result + + # Base case: if the node is null, we've reached past a leaf; backtrack + if node == None: + return + + # Add the current node's value to our running total + currentSum += node.val + + # Append the current node's value to track the path we took to get here + nodesInPath.append(node.val) + + # Check if this is a leaf node (no left or right children) AND + # if our running sum exactly equals the target sum + if node.left == None and node.right == None and currentSum == targetSum: + # We found a valid path! Add this specific path to our final results + result.append(nodesInPath) + + # Recursively explore the left and right subtrees. + # We use copy.deepcopy() to ensure that the left and right branches get their own + # independent copies of the path list, preventing them from modifying each other's paths. + helper(node.left, currentSum, copy.deepcopy(nodesInPath)) + helper(node.right, currentSum, copy.deepcopy(nodesInPath)) + + # Kick off the DFS with the root node, a starting sum of 0, and an empty path + helper(root, 0, []) + return result \ No newline at end of file diff --git a/Problem2.py b/Problem2.py new file mode 100644 index 00000000..16247013 --- /dev/null +++ b/Problem2.py @@ -0,0 +1,41 @@ +## Problem2 (https://leetcode.com/problems/symmetric-tree/) +# 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 isSymmetric(self, root: Optional[TreeNode]) -> bool: + """ + Time Complexity: O(N) + - N is the total number of nodes in the tree. In the worst case, + we have to visit every single node to verify symmetry. + + Space Complexity: O(N) + - The space complexity is determined by the height of the recursion stack. + - In the worst case (a highly unbalanced tree), the recursion depth can + reach O(N). For a completely balanced tree, it would be O(log N). + """ + + def helper(lRoot, rRoot): + # Base Case 1: If both nodes are null, this branch is symmetric. + if lRoot is None and rRoot is None: + return True + + # Base Case 2: If only one node is null (since we passed Base Case 1), + # there is a structural mismatch, meaning it's not symmetric. + if not lRoot or not rRoot: + return False + + # Recursive Step: For two trees to be mirror images of each other: + # 1. Their root node values must be equal. + # 2. The left subtree of the left tree must be a mirror of the right subtree of the right tree. + # 3. The right subtree of the left tree must be a mirror of the left subtree of the right tree. + return (lRoot.val == rRoot.val and + helper(lRoot.left, rRoot.right) and + helper(lRoot.right, rRoot.left)) + + # We start the check by comparing the left and right subtrees of the main root. + # (Assumes the root is not None, which is typical based on LeetCode constraints >= 1 node). + return helper(root.left, root.right) \ No newline at end of file