Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions Problem1-backtracking.py
Original file line number Diff line number Diff line change
@@ -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
52 changes: 52 additions & 0 deletions Problem1.py
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions Problem2.py
Original file line number Diff line number Diff line change
@@ -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)