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
64 changes: 64 additions & 0 deletions Problem1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# https://leetcode.com/problems/validate-binary-search-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 isValidBST(self, root: Optional[TreeNode]) -> bool:
# prev = None
# isValid = True
# def helper(root):
# nonlocal prev, isValid
# if root == None:
# return

# helper(root.left)
# if prev and prev.val >= root.val:
# isValid = False
# prev = root
# helper(root.right)

# helper(root)
# return isValid



"""
Time Complexity: O(N)
- N is the total number of nodes in the binary tree.
- We visit every single node exactly once to check its value against the boundaries.

Space Complexity: O(N) in the worst case, O(log N) in the best/average case
- The space is consumed by the recursion call stack.
- Worst case: O(N) if the tree is completely unbalanced (like a linked list).
- Best/Average case: O(log N) if the tree is balanced, where the height of
the tree dictates the maximum depth of the recursive stack.
"""

# Helper function that passes the valid boundary limits down the tree
def helper(node, minVal, maxVal):
# Base Case: Reaching the end of a branch (an empty node) means
# this path is valid so far.
if node == None:
return True

# Logic: The current node must be strictly greater than the minimum boundary.
# (minVal is updated when we move to the right child of an ancestor).
if minVal is not None and node.val <= minVal:
return False

# Logic: The current node must be strictly less than the maximum boundary.
# (maxVal is updated when we move to the left child of an ancestor).
if maxVal is not None and node.val >= maxVal:
return False

# Recursive Step:
# 1. Check the left subtree: The current node's value becomes the new strict upper limit (maxVal).
# 2. Check the right subtree: The current node's value becomes the new strict lower limit (minVal).
# Both sides must return True for the entire tree to be valid.
return helper(node.left, minVal, node.val) and helper(node.right, node.val, maxVal)

# Start at the root. Initially, there are no upper or lower boundaries.
return helper(root, None, None)
63 changes: 63 additions & 0 deletions Problem2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
# 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 buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
"""
Logic:
1. The first element in the `preorder` array is always the root of the current tree/subtree.
2. We locate this root value in the `inorder` array.
3. In an inorder traversal, everything to the left of the root belongs to the left subtree,
and everything to the right belongs to the right subtree.
4. We use the size of the left subtree (from `inorder`) to figure out which elements in
`preorder` belong to the left vs. right subtrees.
5. We recursively repeat this process for the left and right subtrees.

Time Complexity: O(N^2) in the worst case (where N is the number of nodes).
For each node, we are searching for its index in `inorder` which takes O(N) time.
Additionally, list slicing takes O(N) time. In a skewed tree, the recursion depth
is N, resulting in N * O(N) = O(N^2) time.

Space Complexity: O(N^2) in the worst case. At each recursive call, we are creating new arrays
via list slicing (`inLeft`, `inRight`, etc.). For a skewed tree with depth N,
creating these slices repeatedly consumes O(N^2) memory. The recursion stack
also takes O(N) space.
"""

# Base case: If there are no elements left, the subtree is empty
if len(preorder) == 0:
return None

# The root of the current subtree is always the first element in preorder
rootVal = preorder[0]

# Find the index of this root in the inorder list
inorderRootIdx = -1
for i in range(len(inorder)):
if inorder[i] == rootVal:
inorderRootIdx = i
break

# Split the inorder list into left and right subtrees
inLeft = inorder[0:inorderRootIdx]
inRight = inorder[(inorderRootIdx + 1):len(inorder)]

# Split the preorder list into left and right subtrees
# The number of elements in the left subtree is len(inLeft).
# We take that many elements from preorder (skipping the first element, which is the root).
preLeft = preorder[1:(1 + len(inLeft))]
preRight = preorder[(1 + len(inLeft)):len(preorder)]

# Construct the root node
root = TreeNode(rootVal)

# Recursively construct the left and right subtrees using the sliced arrays
root.left = self.buildTree(preLeft, inLeft)
root.right = self.buildTree(preRight, inRight)

return root