AlgoAtlas

Trees

Phase 1 47 solutions

Trees

Problem List

# Problem LC Difficulty
1 Binary Tree Inorder Traversal 94 🟒 Easy
2 Binary Tree Preorder Traversal 144 🟒 Easy
3 Binary Tree Postorder Traversal 145 🟒 Easy
4 Invert Binary Tree 226 🟒 Easy
5 Maximum Depth of Binary Tree 104 🟒 Easy
6 Diameter of Binary Tree 543 🟒 Easy
7 Balanced Binary Tree 110 🟒 Easy
8 Same Tree 100 🟒 Easy
9 Subtree of Another Tree 572 🟒 Easy
10 Lowest Common Ancestor of BST 235 🟑 Medium
11 Insert into BST 701 🟑 Medium
12 Delete Node in BST 450 🟑 Medium
13 Binary Tree Level Order Traversal 102 🟑 Medium
14 Binary Tree Right Side View 199 🟑 Medium
15 Construct Quad Tree 427 🟑 Medium
16 Count Good Nodes 1448 🟑 Medium
17 Validate BST 98 🟑 Medium
18 Kth Smallest Element in BST 230 🟑 Medium
19 Construct Tree from Preorder and Inorder 105 🟑 Medium
20 House Robber III 337 🟑 Medium
21 Delete Leaves With Given Value 1325 🟑 Medium
22 Binary Tree Maximum Path Sum 124 πŸ”΄ Hard
23 Serialize And Deserialize Binary Tree 297 πŸ”΄ Hard
24 Zigzag Level Order Traversal 103 🟑 Medium
25 Boundary Traversal 545 🟑 Medium
26 Vertical Order Traversal 987 πŸ”΄ Hard
27 Top View of Binary Tree β€” 🟑 Medium
28 Bottom View of Binary Tree β€” 🟑 Medium
29 Symmetric Tree 101 🟒 Easy
30 Root to Leaf Paths 257 🟒 Easy
31 Lowest Common Ancestor of Binary Tree 236 🟑 Medium
32 Maximum Width of Binary Tree 662 🟑 Medium
33 All Nodes Distance K 863 🟑 Medium
34 Burn Binary Tree 2385 πŸ”΄ Hard
35 Count Complete Tree Nodes 222 🟒 Easy
36 Construct BT from Postorder and Inorder 106 🟑 Medium
37 Morris Traversal β€” 🟑 Medium
38 Flatten Binary Tree to Linked List 114 🟑 Medium
39 Search in BST 700 🟒 Easy
40 Floor and Ceil in BST β€” 🟑 Medium
41 Construct BST from Preorder 1008 🟑 Medium
42 Inorder Successor/Predecessor in BST β€” 🟑 Medium
43 Merge Two BSTs β€” 🟑 Medium
44 Two Sum in BST 653 🟒 Easy
45 Recover BST 99 🟑 Medium
46 Largest BST in Binary Tree 1373 πŸ”΄ Hard

Approach Diagrams

DFS vs BFS Decision

flowchart TD
    A[Tree Problem] --> B{"Need level-by-level<br/>or shortest path?"}
    B -- Yes --> C["BFS with Queue<br/>LC 102, 199"]
    B -- No --> D{"Need to explore<br/>all paths?"}
    D -- Yes --> E[DFS]
    E --> F{Order matters?}
    F -- Pre --> G["Preorder: root→left→right<br/>LC 144, 105"]
    F -- In --> H["Inorder: left→root→right<br/>LC 94, 230"]
    F -- Post --> I["Postorder: left→right→root<br/>LC 145, 124"]
    F -- No specific --> J["Any DFS<br/>LC 104, 543, 110"]

Tree Traversal Orders

flowchart LR
    subgraph Preorder["Preorder (NLR)"]
        P1[Visit Root] --> P2[Left Subtree] --> P3[Right Subtree]
    end
    subgraph Inorder["Inorder (LNR) β€” BST sorted order"]
        I1[Left Subtree] --> I2[Visit Root] --> I3[Right Subtree]
    end
    subgraph Postorder["Postorder (LRN)"]
        O1[Left Subtree] --> O2[Right Subtree] --> O3[Visit Root]
    end

BST Operations Flow

flowchart TD
    A[BST Operation] --> B{Type}
    B -- Search/Insert --> C[Compare with root]
    C -- val < root --> D[Go Left]
    C -- val > root --> E[Go Right]
    C -- val == root --> F["Found / Insert here"]
    B -- Delete --> G[Find node]
    G --> H{Children?}
    H -- No child --> I[Remove node]
    H -- One child --> J[Replace with child]
    H -- Two children --> K["Replace with<br/>inorder successor<br/>min of right subtree"]
    B -- Validate --> L["Pass min/max bounds<br/>down recursively"]
    L --> M{node.val in range?}
    M -- Yes --> N["Check children<br/>with updated bounds"]
    M -- No --> O[Invalid BST]

Problem Categories

mindmap
  root((Trees))
    Traversal
      Inorder LC94
      Preorder LC144
      Postorder LC145
      Level Order LC102
      Zigzag Level Order LC103
      Right Side View LC199
      Morris Inorder/Preorder
    View Problems
      Vertical Order LC987
      Top View
      Bottom View
      Boundary Traversal LC545
    BST Operations
      Insert LC701
      Delete LC450
      Validate LC98
      Kth Smallest LC230
      LCA of BST LC235
    Construction
      From Pre+Inorder LC105
      From Post+Inorder LC106
      Serialize/Deserialize LC297
      Flatten to Linked List LC114
    Path Problems
      Max Depth LC104
      Diameter LC543
      Max Path Sum LC124
      Count Good Nodes LC1448
      Root to Leaf Paths LC257
      Nodes at Distance K LC863
    Structural
      Invert LC226
      Same Tree LC100
      Subtree LC572
      Balanced LC110
      Symmetric LC101
      Max Width LC662
      Count Complete Nodes LC222
    Advanced
      House Robber III LC337
      Burn Binary Tree LC2385
      LCA of Binary Tree LC236
      Delete Leaves LC1325
    BST Advanced
      Search in BST LC700
      Floor and Ceil in BST
      Construct BST from Preorder LC1008
      Inorder Successor/Predecessor BST
      Merge Two BSTs
      Two Sum in BST LC653
      Recover BST LC99
      Largest BST in Binary Tree LC1373

Recursive Tree Pattern Template

flowchart TD
    A[solve node] --> B{"Base case:<br/>node is None?"}
    B -- Yes --> C["Return base value<br/>0, True, None, etc."]
    B -- No --> D["Recurse left<br/>left = solve node.left"]
    D --> E["Recurse right<br/>right = solve node.right"]
    E --> F["Combine results<br/>with current node"]
    F --> G["Return result<br/>up to parent"]

Complexity Summary

Problem Time Space Pattern
Inorder/Preorder/Postorder O(n) O(h) DFS recursive/iterative
Level Order / Zigzag O(n) O(w) BFS
Morris Traversal O(n) O(1) Threading
Max Depth O(n) O(h) DFS
Diameter O(n) O(h) DFS, return height
Balanced O(n) O(h) DFS, return height
Same Tree / Subtree O(nΒ·m) O(h) DFS
Symmetric Tree O(n) O(h) DFS mirror compare
Invert Tree O(n) O(h) DFS/BFS
BST Insert/Delete/Search O(h) O(h) BST property
Validate BST O(n) O(h) DFS + bounds
Kth Smallest O(h+k) O(h) Inorder DFS
LCA of BST O(h) O(1) BST split point
LCA of Binary Tree O(n) O(h) DFS both sides
Floor/Ceil in BST O(h) O(1) BST traversal
Construct from Pre+Inorder O(n) O(n) Preorder + hashmap
Construct from Post+Inorder O(n) O(n) Postorder + hashmap
Construct BST from Preorder O(n) O(h) Recursive with bounds
Right Side View O(n) O(w) BFS last per level
Boundary Traversal O(n) O(n) 3-pass: left + leaves + right
Vertical Order O(n log n) O(n) BFS + sort by col
Top/Bottom View O(n) O(n) BFS + first/last per col
Count Good Nodes O(n) O(h) DFS + max so far
Max Path Sum O(n) O(h) DFS, global max
Max Width O(n) O(w) BFS + position indexing
Serialize/Deserialize O(n) O(n) BFS or DFS
Flatten to Linked List O(n) O(1) Morris-like
Nodes at Distance K O(n) O(n) Parent map + BFS
Burn Binary Tree O(n) O(n) Parent map + BFS
Count Complete Tree Nodes O(logΒ²n) O(log n) Height comparison
House Robber III O(n) O(h) DFS, return pair
Delete Leaves O(n) O(h) Postorder DFS
Inorder Successor/Predecessor O(h) O(1) BST traversal
Two Sum in BST O(n) O(n) Inorder + two pointers
Recover BST O(n) O(1) Morris + find violations
Merge Two BSTs O(m+n) O(m+n) Inorder + merge + build
Largest BST Subtree O(n) O(h) Post-order, return tuple

h = tree height (O(log n) balanced, O(n) worst), w = max width


Study Order

flowchart LR
    subgraph W1["Week 1: Basic Traversals"]
        t1["inorder(94) β†’ preorder(144) β†’ postorder(145)"]
        t2["level_order(102) β†’ zigzag(103) β†’ right_side_view(199)"]
    end

    subgraph W2["Week 2: DFS Patterns"]
        t3["invert(226) β†’ max_depth(104) β†’ diameter(543)"]
        t4["balanced(110) β†’ same_tree(100) β†’ subtree(572) β†’ symmetric(101) β†’ count_good_nodes(1448)"]
    end

    subgraph W3["Week 3: View Problems"]
        t5["boundary_traversal β†’ vertical_order(987)"]
        t6["top_view β†’ bottom_view β†’ morris_traversal"]
    end

    subgraph W4["Week 4: BST Operations"]
        t7["search(700) β†’ insert(701) β†’ delete(450) β†’ validate(98)"]
        t8["kth_smallest(230) β†’ lca_bst(235) β†’ floor_ceil β†’ inorder_successor_predecessor"]
        t9["two_sum_bst(653) β†’ construct_bst_from_preorder(1008) β†’ merge_two_bsts"]
    end

    subgraph W5["Week 5: Construction + Path Problems"]
        t10["construct_pre_inorder(105) β†’ construct_post_inorder(106)"]
        t11["serialize_deserialize(297) β†’ flatten(114) β†’ count_complete_nodes(222)"]
        t12["root_to_leaf_paths(257) β†’ lca_binary_tree(236) β†’ max_width(662)"]
    end

    subgraph W6["Week 6: Hard + Advanced"]
        t13["max_path_sum(124) β†’ burn_binary_tree(2385) β†’ all_nodes_distance_k(863)"]
        t14["recover_bst(99) β†’ largest_bst(333) β†’ house_robber_iii(337)"]
        t15["delete_leaves(1325) β†’ construct_quad_tree(427) β†’ morris_traversal"]
    end

    W1 --> W2 --> W3 --> W4 --> W5 --> W6

Key insight: Master the recursive template first (base case β†’ recurse left β†’ recurse right β†’ combine). Every tree problem is a variation of this pattern.

Trees β€” Pattern Notes

Core Intuition

Trees are recursive structures. Almost every tree problem reduces to: what do I need from my children to answer the question at the current node? Define that return value clearly, then trust recursion.

  • DFS = process node + recurse. Choose pre/in/post based on when you need the node's value relative to children.
  • BFS = level-by-level using a queue. Use when you need level info or shortest path.
  • BST = left < node < right. Inorder traversal gives sorted order. Always exploit this property.

Decision Flowchart

flowchart TD
    A[Tree Problem] --> B{Need level-by-level info?}
    B -- Yes --> C[BFS with queue]
    B -- No --> D{Is it a BST?}
    D -- Yes --> E{Exploit sorted order?}
    E -- Yes --> F["Inorder / BST property"]
    E -- No --> G[Standard DFS]
    D -- No --> H{Path problem?}
    H -- Yes --> I{Path through root?}
    I -- Yes --> J[Return value up + track global max]
    I -- No --> K[Pass value down as parameter]
    H -- No --> L{Need parent info?}
    L -- Yes --> M["Pass parent/state as param"]
    L -- No --> N[Pure recursive DFS]

Per-Problem Notes

1. Inorder Traversal β€” LC 94 | Easy

  • Key insight: Left β†’ Node β†’ Right. Iterative uses explicit stack; push left spine, pop, go right.
  • Tricky: Iterative version β€” remember to move curr = curr.right after popping.
  • Common mistakes: Forgetting the right subtree in iterative; confusing with preorder.
  • Time: O(n) | Space: O(h) stack

2. Preorder Traversal β€” LC 144 | Easy

  • Key insight: Node β†’ Left β†’ Right. Iterative: push right then left so left is processed first.
  • Tricky: Iterative push order is reversed (right before left).
  • Common mistakes: Pushing left before right in iterative.
  • Time: O(n) | Space: O(h)

3. Postorder Traversal β€” LC 145 | Easy

  • Key insight: Left β†’ Right β†’ Node. Iterative trick: reverse of modified preorder (Nodeβ†’Rightβ†’Left).
  • Tricky: Iterative is non-obvious; use two-stack or reverse trick.
  • Common mistakes: Trying to directly simulate postorder iteratively without the reverse trick.
  • Time: O(n) | Space: O(h)

4. Invert Binary Tree β€” LC 226 | Easy

  • Key insight: Swap left and right children at every node. Pure recursion.
  • Tricky: Nothing β€” classic recursion warm-up.
  • Common mistakes: Forgetting to return the node after swapping.
  • Time: O(n) | Space: O(h)

5. Maximum Depth β€” LC 104 | Easy

  • Key insight: max(depth(left), depth(right)) + 1. Base case: null β†’ 0.
  • Tricky: Off-by-one β€” depth of a single node is 1, not 0.
  • Common mistakes: Returning 0 for leaf instead of 1.
  • Time: O(n) | Space: O(h)

6. Diameter of Binary Tree β€” LC 543 | Easy

  • Key insight: Diameter through a node = left_height + right_height. Track global max, return height upward.
  • Tricky: The diameter doesn't have to pass through root. Must use a nonlocal/global variable.
  • Common mistakes: Returning diameter instead of height from the recursive function.
  • Time: O(n) | Space: O(h)

7. Balanced Binary Tree β€” LC 110 | Easy

  • Key insight: Return -1 as a sentinel for "unbalanced" up the call stack. Avoids recomputation.
  • Tricky: NaΓ―ve O(nΒ²) solution recomputes heights. Optimal does it in one pass.
  • Common mistakes: Computing height separately for each node (O(nΒ²)).
  • Time: O(n) | Space: O(h)

8. Same Tree β€” LC 100 | Easy

  • Key insight: Two trees are same if roots equal AND left subtrees same AND right subtrees same.
  • Tricky: Handle null cases: both null β†’ true, one null β†’ false.
  • Common mistakes: Not handling the case where one is null and other isn't.
  • Time: O(n) | Space: O(h)

9. Subtree of Another Tree β€” LC 572 | Easy

  • Key insight: At each node of the big tree, check if isSameTree(node, subRoot). Recurse if not.
  • Tricky: O(nΒ·m) is acceptable here. Serialization approach is O(n+m) but complex.
  • Common mistakes: Not checking the current node itself, only children.
  • Time: O(nΒ·m) | Space: O(h)

10. LCA of BST β€” LC 235 | Medium

  • Key insight: If both nodes < root β†’ go left. Both > root β†’ go right. Otherwise root is LCA.
  • Tricky: BST property makes this O(h) instead of O(n).
  • Common mistakes: Using the general LCA approach (LC 236) which ignores BST property.
  • Time: O(h) | Space: O(h)

11. Insert into BST β€” LC 701 | Medium

  • Key insight: Recurse left/right based on comparison. Insert at the null spot.
  • Tricky: Return the node at each step to reattach it to parent.
  • Common mistakes: Not returning the node β€” breaks the tree linkage.
  • Time: O(h) | Space: O(h)

12. Delete Node in BST β€” LC 450 | Medium

  • Key insight: Three cases: no children (delete), one child (replace), two children (replace with inorder successor = leftmost of right subtree).
  • Tricky: Two-children case. Find inorder successor, copy its value, delete successor from right subtree.
  • Common mistakes: Forgetting to recursively delete the successor after copying its value.
  • Time: O(h) | Space: O(h)

13. Binary Tree Level Order β€” LC 102 | Medium

  • Key insight: BFS with queue. Track level size at start of each iteration.
  • Tricky: Snapshot len(queue) before the inner loop, not during.
  • Common mistakes: Not snapshotting queue size β€” processes multiple levels in one pass.
  • Time: O(n) | Space: O(n)

14. Right Side View β€” LC 199 | Medium

  • Key insight: BFS level order β€” take the last element of each level. Or DFS passing depth, update result when depth == len(result).
  • Tricky: DFS approach: visit right child first so rightmost is seen first at each depth.
  • Common mistakes: BFS: taking first element instead of last.
  • Time: O(n) | Space: O(n)

15. Construct Quad Tree β€” LC 427 | Medium

  • Key insight: Check if all values in region are same β†’ leaf. Otherwise split into 4 quadrants recursively.
  • Tricky: Precompute 2D prefix sums to check uniformity in O(1) per region.
  • Common mistakes: Recomputing sum naively β†’ O(nΒ² log n). Use prefix sums for O(nΒ²).
  • Time: O(nΒ²) | Space: O(nΒ²)

16. Count Good Nodes β€” LC 1448 | Medium

  • Key insight: Pass max value seen so far down the tree. Node is "good" if its value β‰₯ max.
  • Tricky: Pass max as parameter (top-down), not bottom-up.
  • Common mistakes: Trying to solve bottom-up when top-down is natural here.
  • Time: O(n) | Space: O(h)

17. Validate BST β€” LC 98 | Medium

  • Key insight: Pass (min, max) bounds down. Each node must satisfy min < node.val < max.
  • Tricky: Initial bounds are (-∞, +∞). Use float('-inf') and float('inf').
  • Common mistakes: Only comparing with direct parent (fails for grandparent violations). Duplicate values β€” BST typically requires strict inequality.
  • Time: O(n) | Space: O(h)

18. Kth Smallest in BST β€” LC 230 | Medium

  • Key insight: Inorder traversal of BST gives sorted order. Stop at kth element.
  • Tricky: Iterative inorder with early exit is more efficient than collecting all.
  • Common mistakes: Collecting entire inorder list then indexing β€” wastes memory.
  • Time: O(h + k) | Space: O(h)

19. Construct Tree from Preorder+Inorder β€” LC 105 | Medium

  • Key insight: Preorder[0] is root. Find root in inorder β†’ splits into left/right subtrees. Recurse.
  • Tricky: Use a hashmap for O(1) inorder index lookup. Track preorder index with nonlocal/pointer.
  • Common mistakes: Linear search in inorder (O(nΒ²)). Off-by-one in index slicing.
  • Time: O(n) | Space: O(n)

20. House Robber III β€” LC 337 | Medium

  • Key insight: At each node, return (rob_this, skip_this). rob_this = node.val + skip_left + skip_right. skip_this = max(rob_left, skip_left) + max(rob_right, skip_right).
  • Tricky: Return a pair from each recursive call to avoid recomputation.
  • Common mistakes: Using memoization with a dict β€” works but the pair-return approach is cleaner and O(n).
  • Time: O(n) | Space: O(h)

21. Delete Leaves with Given Value β€” LC 1325 | Medium

  • Key insight: Postorder β€” process children first, then check if current node became a leaf with target value.
  • Tricky: After deleting children, the parent might now be a leaf with target value too.
  • Common mistakes: Preorder traversal β€” misses nodes that become leaves after children are deleted.
  • Time: O(n) | Space: O(h)

22. Binary Tree Maximum Path Sum β€” LC 124 | Hard

  • Key insight: At each node, max gain = node.val + max(0, left_gain) + max(0, right_gain) for the global answer. But return only node.val + max(0, max(left, right)) upward (can't split).
  • Tricky: The path through a node can't branch when returned to parent. Two different computations: one for global max, one for return value.
  • Common mistakes: Returning the full path sum (with both children) to parent β€” invalid since a path can't fork.
  • Time: O(n) | Space: O(h)

23. Serialize/Deserialize Binary Tree β€” LC 297 | Hard

  • Key insight: Preorder with null markers. Serialize: "1,2,N,N,3,N,N". Deserialize: use a queue/iterator of tokens.
  • Tricky: Deserialize needs a mutable index/iterator. Use collections.deque and popleft().
  • Common mistakes: Using a list index without making it mutable (nonlocal or iterator). Forgetting null markers.
  • Time: O(n) | Space: O(n)

24. Two Sum BSTs β€” LC 1214 | Medium (Premium)

  • Key insight: Inorder of each BST gives a sorted array β†’ classic Two Sum II with two pointers (i from left of tree1's inorder, j from right of tree2's inorder).
  • Tricky: Unlike LC 653 (single BST), you need two structures. Can't do a single-pass hash scan across both trees simultaneously.
  • Alternative: For each node in tree1, BST-search for target - val in tree2. O(nΒ·log m) but O(h) space.
  • Common mistakes: Using LC 653's hash-set approach β€” works but doesn't exploit the two-BST structure optimally.
  • Time: O(n+m) inorder approach | Space: O(n+m)

Edge Cases to Watch For

  • Null/empty tree: Always handle if not root: return ... first.
  • Single node tree: Diameter = 0, depth = 1, it's both a leaf and root.
  • Skewed trees: Stack overflow risk in deep recursion. Height = n for a linked-list tree.
  • BST with duplicates: Clarify if duplicates are allowed and which side they go.
  • Negative values: Max path sum β€” don't assume all values are positive. Use max(0, gain) to ignore negative branches.
  • Path problems: "Path" usually means no revisiting nodes. Clarify if it must go root-to-leaf or any node-to-node.
  • Integer overflow: Max path sum with large values β€” use Python (no overflow) or long in Java/C++.
# Problem LC Difficulty
1 Binary Tree Inorder Traversal 94 🟒 Easy
2 Binary Tree Preorder Traversal 144 🟒 Easy
3 Binary Tree Postorder Traversal 145 🟒 Easy
4 Invert Binary Tree 226 🟒 Easy
5 Maximum Depth of Binary Tree 104 🟒 Easy
6 Diameter of Binary Tree 543 🟒 Easy
7 Balanced Binary Tree 110 🟒 Easy
8 Same Tree 100 🟒 Easy
9 Subtree of Another Tree 572 🟒 Easy
10 Lowest Common Ancestor of BST 235 🟑 Medium
11 Insert into BST 701 🟑 Medium
12 Delete Node in BST 450 🟑 Medium
13 Binary Tree Level Order Traversal 102 🟑 Medium
14 Binary Tree Right Side View 199 🟑 Medium
15 Construct Quad Tree 427 🟑 Medium
16 Count Good Nodes 1448 🟑 Medium
17 Validate BST 98 🟑 Medium
18 Kth Smallest Element in BST 230 🟑 Medium
19 Construct Tree from Preorder and Inorder 105 🟑 Medium
20 House Robber III 337 🟑 Medium
21 Delete Leaves With Given Value 1325 🟑 Medium
22 Binary Tree Maximum Path Sum 124 πŸ”΄ Hard
23 Serialize And Deserialize Binary Tree 297 πŸ”΄ Hard
24 Zigzag Level Order Traversal 103 🟑 Medium
25 Boundary Traversal 545 🟑 Medium
26 Vertical Order Traversal 987 πŸ”΄ Hard
27 Top View of Binary Tree β€” 🟑 Medium
28 Bottom View of Binary Tree β€” 🟑 Medium
29 Symmetric Tree 101 🟒 Easy
30 Root to Leaf Paths 257 🟒 Easy
31 Lowest Common Ancestor of Binary Tree 236 🟑 Medium
32 Maximum Width of Binary Tree 662 🟑 Medium
33 All Nodes Distance K 863 🟑 Medium
34 Burn Binary Tree 2385 πŸ”΄ Hard
35 Count Complete Tree Nodes 222 🟒 Easy
36 Construct BT from Postorder and Inorder 106 🟑 Medium
37 Morris Traversal β€” 🟑 Medium
38 Flatten Binary Tree to Linked List 114 🟑 Medium
39 Search in BST 700 🟒 Easy
40 Floor and Ceil in BST β€” 🟑 Medium
41 Construct BST from Preorder 1008 🟑 Medium
42 Inorder Successor/Predecessor in BST β€” 🟑 Medium
43 Merge Two BSTs β€” 🟑 Medium
44 Two Sum in BST 653 🟒 Easy
45 Recover BST 99 🟑 Medium
46 Largest BST in Binary Tree 1373 πŸ”΄ Hard
All Nodes Distance K β–Ό expand
"""
LC 863 - All Nodes Distance K in Binary Tree

Problem: Find all nodes at distance k from a target node.

Example:
    Input:  [3,5,1,6,2,0,8,null,null,7,4], target=5, k=2
    Output: [7,4,1]

Constraints:
    - Number of nodes: [1, 500], all unique values
    - 0 <= k <= 1000
"""
from typing import List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i]); q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i]); q.append(node.right)
        i += 1
    return root


class Solution:
    def distanceK(self, root: TreeNode, target: TreeNode, k: int) -> List[int]:
        # Approach: build parent map so we can traverse upward,
        # then BFS from target in all 3 directions (left, right, parent) β€” O(n)
        parent = {}

        def build_parents(node, par=None):
            if not node: return
            parent[node] = par
            build_parents(node.left, node)
            build_parents(node.right, node)

        build_parents(root)

        # BFS from target, avoid revisiting
        queue = deque([(target, 0)])
        visited = {target}
        result = []
        while queue:
            node, dist = queue.popleft()
            if dist == k:
                result.append(node.val)
                continue
            for neighbor in (node.left, node.right, parent[node]):
                if neighbor and neighbor not in visited:
                    visited.add(neighbor)
                    queue.append((neighbor, dist + 1))
        return result


if __name__ == "__main__":
    sol = Solution()
    root = build_tree([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4])

    def find(node, val):
        if not node: return None
        if node.val == val: return node
        return find(node.left, val) or find(node.right, val)

    assert sorted(sol.distanceK(root, find(root, 5), 2)) == sorted([7, 4, 1])
    assert sol.distanceK(root, find(root, 5), 0) == [5]
    print("All tests passed.")
Balanced Binary Tree β–Ό expand
"""
# LC 110: Balanced Binary Tree

Given a binary tree, determine if it is height-balanced β€” every node's left and
right subtrees differ in height by at most 1.

## Examples
```text
Input:  root = [3, 9, 20, null, null, 15, 7]
Output: true

Input:  root = [1, 2, 2, 3, 3, null, null, 4, 4]
Output: false

Input:  root = []
Output: true
```

## Constraints
- The number of nodes in the tree is in the range [0, 5000].
- -10^4 <= Node.val <= 10^4
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 110. Balanced Binary Tree

    Given a binary tree, determine if it is height-balanced.

    A height-balanced binary tree is a binary tree in which the depth of the two subtrees
    of every node never differs by more than one.

    ### Examples

    ```text
    Input:  root = [3, 9, 20, null, null, 15, 7]
    Output: true

    Input:  root = [1, 2, 2, 3, 3, null, null, 4, 4]
    Output: false

    Input:  root = []
    Output: true
    ```

    ### Constraints

    - The number of nodes in the tree is in the range `[0, 5000]`.
    - `-10^4 <= Node.val <= 10^4`
    """

    def isBalanced_topdown(self, root: Optional[TreeNode]) -> bool:
        # Time: O(n^2), Space: O(n)
        def height(node):
            if not node:
                return 0
            return 1 + max(height(node.left), height(node.right))

        if not root:
            return True
        if abs(height(root.left) - height(root.right)) > 1:
            return False
        return self.isBalanced_topdown(root.left) and self.isBalanced_topdown(root.right)

    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        # Return height if balanced, -1 if not; propagate -1 upward on imbalance
        # A node is balanced if both subtrees are balanced and heights differ by <= 1
        # Time: O(n), Space: O(n)
        def check(node):
            if not node:
                return 0
            left = check(node.left)
            if left == -1:
                return -1
            right = check(node.right)
            if right == -1:
                return -1
            if abs(left - right) > 1:
                return -1
            return 1 + max(left, right)

        return check(root) != -1


if __name__ == '__main__':
    s = Solution()

    assert s.isBalanced(build_tree([3, 9, 20, None, None, 15, 7])) is True
    assert s.isBalanced_topdown(build_tree([3, 9, 20, None, None, 15, 7])) is True

    assert s.isBalanced(build_tree([1, 2, 2, 3, 3, None, None, 4, 4])) is False
    assert s.isBalanced(None) is True

    print("All tests passed.")
Binary Tree Inorder Traversal β–Ό expand
"""
# LC 94: Binary Tree Inorder Traversal

Return the inorder traversal (left -> root -> right) of a binary tree's node values.

## Examples
```text
Input:  root = [1, null, 2, 3]
Output: [1, 3, 2]

Input:  root = []
Output: []

Input:  root = [1]
Output: [1]
```

## Constraints
- The number of nodes in the tree is in the range [0, 100].
- -100 <= Node.val <= 100
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 94. Binary Tree Inorder Traversal

    Given the `root` of a binary tree, return the inorder traversal of its nodes' values.

    ### Examples

    ```text
    Input:  root = [1, null, 2, 3]
    Output: [1, 3, 2]

    Input:  root = []
    Output: []

    Input:  root = [1]
    Output: [1]
    ```

    ### Constraints

    - The number of nodes in the tree is in the range `[0, 100]`.
    - `-100 <= Node.val <= 100`
    """

    def inorderTraversal_recursive(self, root: Optional[TreeNode]) -> List[int]:
        # Time: O(n), Space: O(n)
        result = []
        def dfs(node):
            if not node:
                return
            dfs(node.left)
            result.append(node.val)
            dfs(node.right)
        dfs(root)
        return result

    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        # Recursive in-order: left subtree, root, right subtree
        # Time: O(n), Space: O(n)
        result, stack = [], []
        curr = root
        while curr or stack:
            while curr:
                stack.append(curr)
                curr = curr.left
            curr = stack.pop()
            result.append(curr.val)
            curr = curr.right
        return result


if __name__ == '__main__':
    s = Solution()

    root = build_tree([1, None, 2, 3])
    assert s.inorderTraversal(root) == [1, 3, 2]
    assert s.inorderTraversal_recursive(build_tree([1, None, 2, 3])) == [1, 3, 2]

    assert s.inorderTraversal(None) == []
    assert s.inorderTraversal(build_tree([1])) == [1]

    print("All tests passed.")
Binary Tree Level Order Traversal β–Ό expand
"""
# LC 102: Binary Tree Level Order Traversal

Return the level-order traversal of a binary tree as a list of lists (one list per
level, left to right).

## Examples
```text
Input:  root = [3, 9, 20, null, null, 15, 7]
Output: [[3], [9, 20], [15, 7]]

Input:  root = [1]
Output: [[1]]

Input:  root = []
Output: []
```

## Constraints
- The number of nodes in the tree is in the range [0, 2000].
- -1000 <= Node.val <= 1000
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 102. Binary Tree Level Order Traversal

    Given the `root` of a binary tree, return the level order traversal of its nodes' values
    (i.e., from left to right, level by level).

    ### Examples

    ```text
    Example 1:
    Input:  root = [3,9,20,null,null,15,7]
    Output: [[3],[9,20],[15,7]]

    Example 2:
    Input:  root = [1]
    Output: [[1]]

    Example 3:
    Input:  root = []
    Output: []
    ```

    ### Constraints
    - The number of nodes in the tree is in the range `[0, 2000]`.
    - `-1000 <= Node.val <= 1000`
    """

    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        # BFS with a queue; process all nodes at current level before moving to next
        # Time: O(n), Space: O(n) β€” process nodes level by level using a queue
        if not root:
            return []
        result, queue = [], deque([root])
        while queue:
            level = []
            for _ in range(len(queue)):
                node = queue.popleft()
                level.append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            result.append(level)
        return result

    def levelOrder_dfs(self, root: Optional[TreeNode]) -> List[List[int]]:
        # DFS with depth index; extend result list when reaching a new depth level
        # Time: O(n), Space: O(n) β€” use recursion depth as level index
        result = []

        def dfs(node: Optional[TreeNode], depth: int) -> None:
            if not node:
                return
            if depth == len(result):
                result.append([])
            result[depth].append(node.val)
            dfs(node.left, depth + 1)
            dfs(node.right, depth + 1)

        dfs(root, 0)
        return result


if __name__ == '__main__':
    sol = Solution()

    root = build_tree([3, 9, 20, None, None, 15, 7])
    assert sol.levelOrder(root) == [[3], [9, 20], [15, 7]]
    assert sol.levelOrder_dfs(root) == [[3], [9, 20], [15, 7]]

    assert sol.levelOrder(build_tree([1])) == [[1]]
    assert sol.levelOrder(None) == []

    print("All tests passed.")
Binary Tree Maximum Path Sum β–Ό expand
"""
# LC 124: Binary Tree Maximum Path Sum

Find the maximum sum of any path in a binary tree. A path is any sequence of nodes
connected by edges; it does not need to pass through the root.

## Examples
```text
Input:  root = [1, 2, 3]
Output: 6

Input:  root = [-10, 9, 20, null, null, 15, 7]
Output: 42
```

## Constraints
- The number of nodes in the tree is in the range [1, 3 * 10^4].
- -1000 <= Node.val <= 1000
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 124. Binary Tree Maximum Path Sum

    A **path** in a binary tree is a sequence of nodes where each pair of adjacent nodes in
    the sequence has an edge connecting them. A node can only appear in the sequence **at most
    once**. Note that the path does not need to pass through the root.

    The **path sum** of a path is the sum of the node's values in the path.

    Given the `root` of a binary tree, return the maximum path sum of any **non-empty** path.

    ---

    ### Examples

    ```text
    Example 1:
        Input:  root = [1, 2, 3]
        Output: 6
        Explanation: The optimal path is 2 -> 1 -> 3 with a path sum of 2 + 1 + 3 = 6.

    Example 2:
        Input:  root = [-10, 9, 20, null, null, 15, 7]
        Output: 42
        Explanation: The optimal path is 15 -> 20 -> 7 with a path sum of 15 + 20 + 7 = 42.
    ```

    ---

    ### Constraints

    - The number of nodes in the tree is in the range [1, 3 * 10^4].
    - -1000 <= Node.val <= 1000
    """

    def maxPathSum(self, root: Optional[TreeNode]) -> int:  # O(n) time, O(h) space
        # For each node, max path through it = node.val + max(0, left) + max(0, right)
        # Update global max; return node.val + max(0, best single branch) to parent
        self.max_sum = float('-inf')

        def dfs(node):
            if not node:
                return 0
            left = max(dfs(node.left), 0)
            right = max(dfs(node.right), 0)
            self.max_sum = max(self.max_sum, node.val + left + right)
            return node.val + max(left, right)

        dfs(root)
        return self.max_sum


if __name__ == '__main__':
    s = Solution()

    root1 = build_tree([1, 2, 3])
    assert s.maxPathSum(root1) == 6

    root2 = build_tree([-10, 9, 20, None, None, 15, 7])
    assert s.maxPathSum(root2) == 42

    root3 = build_tree([-3])
    assert s.maxPathSum(root3) == -3

    print("All tests passed.")
Binary Tree Postorder Traversal β–Ό expand
"""
# LC 145: Binary Tree Postorder Traversal

Return the postorder traversal (left -> right -> root) of a binary tree's node values.

## Examples
```text
Input:  root = [1, null, 2, 3]
Output: [3, 2, 1]

Input:  root = []
Output: []

Input:  root = [1]
Output: [1]
```

## Constraints
- The number of nodes in the tree is in the range [0, 100].
- -100 <= Node.val <= 100
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 145. Binary Tree Postorder Traversal

    Given the `root` of a binary tree, return the postorder traversal of its nodes' values.

    ### Examples

    ```text
    Input:  root = [1, null, 2, 3]
    Output: [3, 2, 1]

    Input:  root = []
    Output: []

    Input:  root = [1]
    Output: [1]
    ```

    ### Constraints

    - The number of nodes in the tree is in the range `[0, 100]`.
    - `-100 <= Node.val <= 100`
    """

    def postorderTraversal_recursive(self, root: Optional[TreeNode]) -> List[int]:
        # Time: O(n), Space: O(n)
        result = []
        def dfs(node):
            if not node:
                return
            dfs(node.left)
            dfs(node.right)
            result.append(node.val)
        dfs(root)
        return result

    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        # Recursive post-order: left subtree, right subtree, root
        # Time: O(n), Space: O(n)
        if not root:
            return []
        stack1, stack2 = [root], []
        while stack1:
            node = stack1.pop()
            stack2.append(node.val)
            if node.left:
                stack1.append(node.left)
            if node.right:
                stack1.append(node.right)
        return stack2[::-1]


if __name__ == '__main__':
    s = Solution()

    assert s.postorderTraversal(build_tree([1, None, 2, 3])) == [3, 2, 1]
    assert s.postorderTraversal_recursive(build_tree([1, None, 2, 3])) == [3, 2, 1]

    assert s.postorderTraversal(None) == []
    assert s.postorderTraversal(build_tree([1])) == [1]

    print("All tests passed.")
Binary Tree Preorder Traversal β–Ό expand
"""
# LC 144: Binary Tree Preorder Traversal

Return the preorder traversal (root -> left -> right) of a binary tree's node values.

## Examples
```text
Input:  root = [1, null, 2, 3]
Output: [1, 2, 3]

Input:  root = []
Output: []

Input:  root = [1]
Output: [1]
```

## Constraints
- The number of nodes in the tree is in the range [0, 100].
- -100 <= Node.val <= 100
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 144. Binary Tree Preorder Traversal

    Given the `root` of a binary tree, return the preorder traversal of its nodes' values.

    ### Examples

    ```text
    Input:  root = [1, null, 2, 3]
    Output: [1, 2, 3]

    Input:  root = []
    Output: []

    Input:  root = [1]
    Output: [1]
    ```

    ### Constraints

    - The number of nodes in the tree is in the range `[0, 100]`.
    - `-100 <= Node.val <= 100`
    """

    def preorderTraversal_recursive(self, root: Optional[TreeNode]) -> List[int]:
        # Time: O(n), Space: O(n)
        result = []
        def dfs(node):
            if not node:
                return
            result.append(node.val)
            dfs(node.left)
            dfs(node.right)
        dfs(root)
        return result

    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        # Recursive pre-order: root, left subtree, right subtree
        # Time: O(n), Space: O(n)
        if not root:
            return []
        result, stack = [], [root]
        while stack:
            node = stack.pop()
            result.append(node.val)
            if node.right:
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        return result


if __name__ == '__main__':
    s = Solution()

    assert s.preorderTraversal(build_tree([1, None, 2, 3])) == [1, 2, 3]
    assert s.preorderTraversal_recursive(build_tree([1, None, 2, 3])) == [1, 2, 3]

    assert s.preorderTraversal(None) == []
    assert s.preorderTraversal(build_tree([1])) == [1]

    print("All tests passed.")
Binary Tree Right Side View β–Ό expand
"""
# LC 199: Binary Tree Right Side View

Return the values of nodes visible when looking at the tree from the right side,
ordered top to bottom.

## Examples
```text
Input:  root = [1, 2, 3, null, 5, null, 4]
Output: [1, 3, 4]

Input:  root = [1, null, 3]
Output: [1, 3]

Input:  root = []
Output: []
```

## Constraints
- The number of nodes in the tree is in the range [0, 100].
- -100 <= Node.val <= 100
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 199. Binary Tree Right Side View

    Given the `root` of a binary tree, imagine yourself standing on the right side of it.
    Return the values of the nodes you can see ordered from top to bottom.

    ### Examples

    ```text
    Example 1:
    Input:  root = [1,2,3,null,5,null,4]
    Output: [1,3,4]

    Example 2:
    Input:  root = [1,null,3]
    Output: [1,3]

    Example 3:
    Input:  root = []
    Output: []
    ```

    ### Constraints
    - The number of nodes in the tree is in the range `[0, 100]`.
    - `-100 <= Node.val <= 100`
    """

    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
        # BFS level by level; the last node in each level is visible from the right
        # Time: O(n), Space: O(n) β€” take the last node of each BFS level
        if not root:
            return []
        result, queue = [], deque([root])
        while queue:
            level_size = len(queue)
            for i in range(level_size):
                node = queue.popleft()
                if i == level_size - 1:
                    result.append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
        return result

    def rightSideView_dfs(self, root: Optional[TreeNode]) -> List[int]:
        # Time: O(n), Space: O(h) β€” visit right child first; first visit at each depth is visible
        result = []

        def dfs(node: Optional[TreeNode], depth: int) -> None:
            if not node:
                return
            if depth == len(result):
                result.append(node.val)
            dfs(node.right, depth + 1)
            dfs(node.left, depth + 1)

        dfs(root, 0)
        return result


if __name__ == '__main__':
    sol = Solution()

    root = build_tree([1, 2, 3, None, 5, None, 4])
    assert sol.rightSideView(root) == [1, 3, 4]
    assert sol.rightSideView_dfs(root) == [1, 3, 4]

    root2 = build_tree([1, None, 3])
    assert sol.rightSideView(root2) == [1, 3]
    assert sol.rightSideView_dfs(root2) == [1, 3]

    assert sol.rightSideView(None) == []

    print("All tests passed.")
Bottom View Of Binary Tree β–Ό expand
"""
GFG - Bottom View of Binary Tree

Problem: Return the bottom view of a binary tree β€” the last node visible at each
horizontal distance when viewed from below.

Example:
    Input:  [1,2,3,4,5,6,7]
    Output: [4,2,6,3,7]

Constraints:
    - Number of nodes: [1, 10^5]
    - 1 <= Node.val <= 10^5
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            q.append(node.right)
        i += 1
    return root


class Solution:
    def bottomView(self, root: Optional[TreeNode]) -> List[int]:
        # Approach: BFS with column tracking; keep LAST seen per column β€” O(n)
        if not root:
            return []

        col_map = {}  # col -> last node val seen (bottom-most)
        queue = deque([(root, 0)])  # (node, col)
        while queue:
            node, col = queue.popleft()
            col_map[col] = node.val  # always overwrite = last seen = bottom view
            if node.left:  queue.append((node.left,  col - 1))
            if node.right: queue.append((node.right, col + 1))

        return [col_map[c] for c in sorted(col_map)]


if __name__ == "__main__":
    sol = Solution()

    root = build_tree([1, 2, 3, 4, 5, 6, 7])
    assert sol.bottomView(root) == [4, 2, 6, 3, 7]

    root2 = build_tree([1, 2, 3])
    assert sol.bottomView(root2) == [2, 1, 3]

    print("All tests passed.")
Boundary Traversal β–Ό expand
"""
LC 545 / GFG - Boundary Traversal of Binary Tree

Problem: Return the boundary of a binary tree anticlockwise starting from root.
Boundary = left boundary (excl. leaves) + all leaves + right boundary reversed (excl. leaves).

Example:
    Input:  [1,2,3,4,5,6,7]
    Output: [1,2,4,5,6,7,3]

Constraints:
    - Number of nodes: [1, 10^4]
    - -1000 <= Node.val <= 1000
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            q.append(node.right)
        i += 1
    return root


class Solution:
    def boundaryOfBinaryTree(self, root: Optional[TreeNode]) -> List[int]:
        # Approach: three passes β€” left boundary, leaves, right boundary β€” O(n)
        if not root:
            return []

        def is_leaf(node):
            return not node.left and not node.right

        def left_boundary(node):
            res = []
            while node:
                if not is_leaf(node):
                    res.append(node.val)
                # prefer left child, fall back to right
                node = node.left if node.left else node.right
            return res

        def leaves(node):
            if not node: return []
            if is_leaf(node): return [node.val]
            return leaves(node.left) + leaves(node.right)

        def right_boundary(node):
            res = []
            while node:
                if not is_leaf(node):
                    res.append(node.val)
                # prefer right child, fall back to left
                node = node.right if node.right else node.left
            return res[::-1]  # reverse so it goes bottom-up

        return (
            [root.val]
            + left_boundary(root.left)
            + leaves(root.left)
            + leaves(root.right)
            + right_boundary(root.right)
        )


if __name__ == "__main__":
    sol = Solution()

    root = build_tree([1, 2, 3, 4, 5, 6, 7])
    assert sol.boundaryOfBinaryTree(root) == [1, 2, 4, 5, 6, 7, 3]

    root2 = build_tree([1])
    assert sol.boundaryOfBinaryTree(root2) == [1]

    print("All tests passed.")
Burn Binary Tree β–Ό expand
"""
LC 2385 - Amount of Time for Binary Tree to Be Infected

Problem: Starting from node `start`, fire spreads to adjacent nodes each second.
Return the minimum time to burn the entire tree.

Example:
    Input:  [1,5,3,null,4,10,6,9,2], start=3
    Output: 4

Constraints:
    - Number of nodes: [1, 10^5], all unique
    - 1 <= start <= 10^5
"""
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i]); q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i]); q.append(node.right)
        i += 1
    return root


class Solution:
    def amountOfTime(self, root: Optional[TreeNode], start: int) -> int:
        # Approach: build parent map + locate start node,
        # then BFS spreading in all directions β€” O(n)
        parent = {}
        start_node = None

        def build(node, par=None):
            nonlocal start_node
            if not node: return
            parent[node] = par
            if node.val == start: start_node = node
            build(node.left, node)
            build(node.right, node)

        build(root)

        visited = {start_node}
        queue = deque([start_node])
        time = -1
        while queue:
            time += 1
            for _ in range(len(queue)):
                node = queue.popleft()
                for nb in (node.left, node.right, parent[node]):
                    if nb and nb not in visited:
                        visited.add(nb)
                        queue.append(nb)
        return time


if __name__ == "__main__":
    sol = Solution()
    assert sol.amountOfTime(build_tree([1, 5, 3, None, 4, 10, 6, 9, 2]), 3) == 4
    assert sol.amountOfTime(build_tree([1]), 1) == 0
    print("All tests passed.")
Construct Binary Tree From Preorder And Inorder β–Ό expand
"""
# LC 105: Construct Binary Tree from Preorder and Inorder Traversal

Given preorder and inorder traversal arrays of a binary tree (all values unique),
reconstruct and return the tree.

## Examples
```text
Input:  preorder = [3, 9, 20, 15, 7], inorder = [9, 3, 15, 20, 7]
Output: [3, 9, 20, null, null, 15, 7]

Input:  preorder = [-1], inorder = [-1]
Output: [-1]
```

## Constraints
- 1 <= preorder.length <= 3000
- inorder.length == preorder.length
- preorder and inorder consist of unique values.
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree_from_list(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_list(root):
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        if node:
            result.append(node.val)
            queue.append(node.left)
            queue.append(node.right)
        else:
            result.append(None)
    while result and result[-1] is None:
        result.pop()
    return result


class Solution:
    """
    ## 105. Construct Binary Tree from Preorder and Inorder Traversal

    Given two integer arrays `preorder` and `inorder` where:
    - `preorder` is the preorder traversal of a binary tree, and
    - `inorder` is the inorder traversal of the same tree,

    construct and return the binary tree.

    ---

    ### Examples

    ```text
    Example 1:
        Input:  preorder = [3, 9, 20, 15, 7], inorder = [9, 3, 15, 20, 7]
        Output: [3, 9, 20, null, null, 15, 7]

    Example 2:
        Input:  preorder = [-1], inorder = [-1]
        Output: [-1]
    ```

    ---

    ### Constraints

    - 1 <= preorder.length <= 3000
    - inorder.length == preorder.length
    - -3000 <= preorder[i], inorder[i] <= 3000
    - `preorder` and `inorder` consist of unique values.
    - Each value of `inorder` also appears in `preorder`.
    - `preorder` is guaranteed to be the preorder traversal of the tree.
    - `inorder` is guaranteed to be the inorder traversal of the tree.
    """

    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:  # O(n^2) time, O(n) space
        # Preorder[0] is always the root; find it in inorder to split left/right
        # Left subtree has len(left_inorder) nodes in preorder; recurse on both halves
        if not preorder:
            return None
        root_val = preorder[0]
        mid = inorder.index(root_val)
        root = TreeNode(root_val)
        root.left = self.buildTree(preorder[1:mid + 1], inorder[:mid])
        root.right = self.buildTree(preorder[mid + 1:], inorder[mid + 1:])
        return root

    def buildTree_hashmap(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:  # O(n) time, O(n) space
        idx_map = {val: i for i, val in enumerate(inorder)}
        self.pre_idx = 0

        def helper(left, right):
            if left > right:
                return None
            root_val = preorder[self.pre_idx]
            self.pre_idx += 1
            root = TreeNode(root_val)
            mid = idx_map[root_val]
            root.left = helper(left, mid - 1)
            root.right = helper(mid + 1, right)
            return root

        return helper(0, len(inorder) - 1)


if __name__ == '__main__':
    s = Solution()

    pre, ino = [3, 9, 20, 15, 7], [9, 3, 15, 20, 7]
    assert tree_to_list(s.buildTree(pre, ino)) == [3, 9, 20, None, None, 15, 7]
    assert tree_to_list(s.buildTree_hashmap(pre, ino)) == [3, 9, 20, None, None, 15, 7]

    pre2, ino2 = [-1], [-1]
    assert tree_to_list(s.buildTree(pre2, ino2)) == [-1]
    assert tree_to_list(s.buildTree_hashmap(pre2, ino2)) == [-1]

    print("All tests passed.")
Construct Bst From Preorder β–Ό expand
"""
# LC 1008: Construct BST from Preorder Traversal

Given an array of integers `preorder` representing the preorder traversal of a BST,
construct and return the BST.

## Examples
```text
Input:  preorder = [8, 5, 1, 7, 10, 12]
Output: [8, 5, 10, 1, 7, null, 12]
```

## Constraints
- 1 <= preorder.length <= 100
- 1 <= preorder[i] <= 1000
- All the values of preorder are unique.
"""
from __future__ import annotations
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(values: list) -> Optional[TreeNode]:
    if not values or values[0] is None:
        return None
    root = TreeNode(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if i < len(values) and values[i] is not None:
            node.left = TreeNode(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = TreeNode(values[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_inorder(root: Optional[TreeNode]) -> list:
    result = []
    def dfs(node):
        if node:
            dfs(node.left)
            result.append(node.val)
            dfs(node.right)
    dfs(root)
    return result


class Solution:
    # --- Approach 1: Recursive with upper bound O(n) ---
    def bstFromPreorder_recursive(self, preorder: List[int]) -> Optional[TreeNode]:
        self.idx = 0

        def build(upper: float) -> Optional[TreeNode]:
            # If all elements consumed or current value exceeds upper bound, stop
            if self.idx == len(preorder) or preorder[self.idx] > upper:
                return None
            val = preorder[self.idx]
            self.idx += 1
            node = TreeNode(val)
            node.left = build(val)        # left subtree: values < val
            node.right = build(upper)     # right subtree: values < upper
            return node

        return build(float('inf'))

    # --- Approach 2: Stack-based O(n) ---
    def bstFromPreorder(self, preorder: List[int]) -> Optional[TreeNode]:
        if not preorder:
            return None
        root = TreeNode(preorder[0])
        stack = [root]

        for val in preorder[1:]:
            node = TreeNode(val)
            if val < stack[-1].val:
                stack[-1].left = node      # smaller than top β†’ left child
            else:
                # pop until we find the parent (last node smaller than val)
                parent = stack[-1]
                while stack and stack[-1].val < val:
                    parent = stack.pop()
                parent.right = node        # attach as right child
            stack.append(node)

        return root


# ---------- Tests ----------
if __name__ == "__main__":
    sol = Solution()

    # Test 1: standard case
    preorder = [8, 5, 1, 7, 10, 12]
    expected_inorder = sorted(preorder)

    t1 = sol.bstFromPreorder(preorder)
    assert tree_to_inorder(t1) == expected_inorder, f"Got {tree_to_inorder(t1)}"

    t2 = sol.bstFromPreorder_recursive(preorder)
    assert tree_to_inorder(t2) == expected_inorder

    # Test 2: single element
    t = sol.bstFromPreorder([1])
    assert t.val == 1 and t.left is None and t.right is None

    # Test 3: sorted ascending (right-skewed)
    preorder = [1, 2, 3, 4]
    t = sol.bstFromPreorder(preorder)
    assert tree_to_inorder(t) == [1, 2, 3, 4]

    # Test 4: sorted descending (left-skewed)
    preorder = [4, 3, 2, 1]
    t = sol.bstFromPreorder(preorder)
    assert tree_to_inorder(t) == [1, 2, 3, 4]

    print("All tests passed!")
Construct Bt From Postorder And Inorder β–Ό expand
"""
LC 106 - Construct Binary Tree from Inorder and Postorder Traversal

Problem: Build a binary tree given its inorder and postorder traversals.

Example:
    Input:  inorder=[9,3,15,20,7], postorder=[9,15,7,20,3]
    Output: [3,9,20,null,null,15,7]

Constraints:
    - 1 <= len(inorder) <= 3000, all values unique
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i]); q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i]); q.append(node.right)
        i += 1
    return root


class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        # Approach: last element of postorder is root; find it in inorder to
        # split left/right subtrees. Hash map gives O(1) lookup β€” O(n) total.
        idx_map = {val: i for i, val in enumerate(inorder)}

        def helper(in_left, in_right):
            if in_left > in_right: return None
            root_val = postorder.pop()          # rightmost = current root
            root = TreeNode(root_val)
            mid = idx_map[root_val]
            # build right before left (postorder pops from end)
            root.right = helper(mid + 1, in_right)
            root.left  = helper(in_left, mid - 1)
            return root

        return helper(0, len(inorder) - 1)


if __name__ == "__main__":
    from collections import deque

    def tree_to_list(root):
        if not root: return []
        res, q = [], deque([root])
        while q:
            node = q.popleft()
            res.append(node.val if node else None)
            if node:
                q.append(node.left)
                q.append(node.right)
        while res and res[-1] is None: res.pop()
        return res

    sol = Solution()
    root = sol.buildTree([9, 3, 15, 20, 7], [9, 15, 7, 20, 3])
    assert tree_to_list(root) == [3, 9, 20, None, None, 15, 7]
    print("All tests passed.")
Construct Quad Tree β–Ό expand
"""
# LC 427: Construct Quad Tree

Given an n x n binary grid (n is a power of 2), construct a Quad-Tree. Each node is a
leaf if its region is uniform (all 0s or all 1s); otherwise split into four equal
quadrants and recurse.

## Examples
```text
Input:  grid = [[0, 1], [1, 0]]
Output: [[0, 1], [1, 0], [1, 1], [1, 1], [1, 0]]

Input:  grid = [[1, 1], [1, 1]]
Output: [[1, 1]]
```

## Constraints
- n == grid.length == grid[i].length
- n == 2^x where 0 <= x <= 6
- grid[i][j] is 0 or 1.
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Node:
    def __init__(self, val, isLeaf, topLeft=None, topRight=None, bottomLeft=None, bottomRight=None):
        self.val = val
        self.isLeaf = isLeaf
        self.topLeft = topLeft
        self.topRight = topRight
        self.bottomLeft = bottomLeft
        self.bottomRight = bottomRight


class Solution:
    """
    ## 427. Construct Quad Tree

    Given a `n * n` matrix `grid` of `0`s and `1`s, return the corresponding Quad-Tree.

    A Quad-Tree is a tree data structure in which each internal node has exactly four children.
    Each node has two attributes:
    - `val`: `True` if the node represents a grid of all `1`s, or `False` if all `0`s. For
      non-leaf nodes, the value can be either `True` or `False`.
    - `isLeaf`: `True` if the node is a leaf node, or `False` if it has four children.

    We can construct a Quad-Tree from a 2D area using the following steps:
    1. If the current grid has the same value (all `1`s or all `0`s), set `isLeaf` to `True`
       and set `val` to the value of the grid and set the four children to `None`.
    2. If the current grid has different values, set `isLeaf` to `False` and set `val` to any
       value and divide the current grid into four sub-grids.
    3. Recurse for each of the children with the proper sub-grid.

    ### Examples

    ```text
    Example 1:
    Input:  grid = [[0,1],[1,0]]
    Output: [[False,False],[True,1],[True,1],[True,1],[True,0]]
    (Serialized quad-tree format)

    Example 2:
    Input:  grid = [[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,1,1,1,1],
                    [1,1,1,1,1,1,1,1],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],
                    [1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0]]
    Output: [[False,False],[True,1],[True,1],[False,False],[True,1],[True,1],[True,0],[True,0],[True,0]]
    ```

    ### Constraints
    - `n == grid.length == grid[i].length`
    - `n == 2^x` where `0 <= x <= 6`
    """

    def construct(self, grid: List[List[int]]) -> 'Node':
        # Check if all values in region are the same β€” if so, create leaf node
        # Otherwise split into 4 quadrants and recurse on each
        # Time: O(n^2 log n), Space: O(log n) β€” recursively split grid into four quadrants
        def build(r: int, c: int, size: int) -> Node:
            val = grid[r][c]
            all_same = all(grid[r + dr][c + dc] == val
                           for dr in range(size) for dc in range(size))
            if all_same:
                return Node(bool(val), True)
            half = size // 2
            return Node(
                True, False,
                build(r, c, half),
                build(r, c + half, half),
                build(r + half, c, half),
                build(r + half, c + half, half),
            )

        return build(0, 0, len(grid))


if __name__ == '__main__':
    sol = Solution()

    result = sol.construct([[0, 1], [1, 0]])
    assert result.isLeaf == False
    assert result.topLeft.isLeaf == True and result.topLeft.val == False
    assert result.topRight.isLeaf == True and result.topRight.val == True

    result2 = sol.construct([[1]])
    assert result2.isLeaf == True and result2.val == True

    result3 = sol.construct([[0]])
    assert result3.isLeaf == True and result3.val == False

    print("All tests passed.")
Count Complete Tree Nodes β–Ό expand
"""
LC 222 - Count Complete Tree Nodes

Problem: Count nodes in a complete binary tree more efficiently than O(n).

Example:
    Input:  [1,2,3,4,5,6]
    Output: 6

Constraints:
    - Number of nodes: [0, 5 * 10^4]
    - Complete binary tree guaranteed
"""
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i]); q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i]); q.append(node.right)
        i += 1
    return root


class Solution:
    def countNodes(self, root: Optional[TreeNode]) -> int:
        # Approach 1: simple DFS β€” O(n)
        if not root: return 0
        return 1 + self.countNodes(root.left) + self.countNodes(root.right)

    def countNodes_fast(self, root: Optional[TreeNode]) -> int:
        # Approach 2: compare left-most and right-most heights.
        # If equal, left subtree is a perfect binary tree β†’ skip it β€” O(log^2 n)
        if not root: return 0

        left_h, right_h = 0, 0
        l, r = root, root
        while l: left_h  += 1; l = l.left   # height going left
        while r: right_h += 1; r = r.right  # height going right

        if left_h == right_h:
            return (1 << left_h) - 1  # perfect tree: 2^h - 1 nodes

        # not perfect: recurse on both subtrees
        return 1 + self.countNodes_fast(root.left) + self.countNodes_fast(root.right)


if __name__ == "__main__":
    sol = Solution()
    assert sol.countNodes(build_tree([1, 2, 3, 4, 5, 6])) == 6
    assert sol.countNodes_fast(build_tree([1, 2, 3, 4, 5, 6])) == 6
    assert sol.countNodes_fast(build_tree([])) == 0
    assert sol.countNodes_fast(build_tree([1])) == 1
    print("All tests passed.")
Count Good Nodes β–Ό expand
"""
# LC 1448: Count Good Nodes in Binary Tree

A node X is "good" if no node on the path from root to X has a value greater than X.
Return the count of good nodes in the tree.

## Examples
```text
Input:  root = [3, 1, 4, 3, null, 1, 5]
Output: 4

Input:  root = [3, 3, null, 4, 2]
Output: 3

Input:  root = [1]
Output: 1
```

## Constraints
- The number of nodes in the binary tree is in the range [1, 10^5].
- Each node's value is between [-10^4, 10^4].
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 1448. Count Good Nodes in Binary Tree

    Given a binary tree `root`, a node X in the tree is named **good** if in the path from root
    to X there are no nodes with a value greater than X.

    Return the number of good nodes in the binary tree.

    ### Examples

    ```text
    Example 1:
    Input:  root = [3,1,4,3,null,1,5]
    Output: 4
    Explanation: Nodes 3 (root), 3 (left-left), 4 (right), and 5 (right-right) are good.

    Example 2:
    Input:  root = [3,3,null,4,2]
    Output: 3
    Explanation: Nodes 3 (root), 3 (left), and 4 (left-left) are good.

    Example 3:
    Input:  root = [1]
    Output: 1
    ```

    ### Constraints
    - The number of nodes in the binary tree is in the range `[1, 10^5]`.
    - Each node's value is between `[-10^4, 10^4]`.
    """

    def goodNodes(self, root: TreeNode) -> int:
        # A node is 'good' if its value >= max value seen on path from root
        # DFS passing the running max; count good nodes bottom-up
        # Time: O(n), Space: O(h) β€” DFS tracking the max value seen on the path from root
        def dfs(node: Optional[TreeNode], max_so_far: int) -> int:
            if not node:
                return 0
            count = 1 if node.val >= max_so_far else 0
            new_max = max(max_so_far, node.val)
            return count + dfs(node.left, new_max) + dfs(node.right, new_max)

        return dfs(root, root.val)


if __name__ == '__main__':
    sol = Solution()

    root = build_tree([3, 1, 4, 3, None, 1, 5])
    assert sol.goodNodes(root) == 4

    root2 = build_tree([3, 3, None, 4, 2])
    assert sol.goodNodes(root2) == 3

    root3 = build_tree([1])
    assert sol.goodNodes(root3) == 1

    print("All tests passed.")
Delete Leaves With Given Value β–Ό expand
"""
# LC 1325: Delete Leaves With a Given Value

Delete all leaf nodes with value equal to `target`. Repeat until no such leaves remain
(a node that becomes a leaf after deletion should also be deleted if its value equals
`target`).

## Examples
```text
Input:  root = [1, 2, 3, 2, null, 2, 4], target = 2
Output: [1, null, 3, null, 4]

Input:  root = [1, 3, 3, 3, 2], target = 3
Output: [1, 3, null, null, 2]
```

## Constraints
- The number of nodes in the tree is in the range [1, 3000].
- 1 <= Node.val, target <= 1000
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_list(root):
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        if node:
            result.append(node.val)
            queue.append(node.left)
            queue.append(node.right)
        else:
            result.append(None)
    while result and result[-1] is None:
        result.pop()
    return result


class Solution:
    """
    ## 1325. Delete Leaves With a Given Value

    Given a binary tree `root` and an integer `target`, delete all the **leaf nodes** with
    value `target`.

    Note that once you delete a leaf node with value `target`, if its parent node becomes a
    leaf node and has the value `target`, it should also be deleted (you need to continue
    doing this until you can't).

    ---

    ### Examples

    ```text
    Example 1:
        Input:  root = [1, 2, 3, 2, null, 2, 4], target = 2
        Output: [1, null, 3, null, 4]
        Explanation: Leaf nodes with value 2 are removed. Node 2 (left child of root)
                     then becomes a leaf with value 2 and is also removed.

    Example 2:
        Input:  root = [1, 3, 3, 3, 2], target = 3
        Output: [1, 3, null, null, 2]

    Example 3:
        Input:  root = [1, 2, null, 2, null, 2], target = 2
        Output: [1]
    ```

    ---

    ### Constraints

    - The number of nodes in the tree is in the range [1, 3000].
    - 1 <= Node.val, target <= 1000
    """

    def removeLeafNodes(self, root: Optional[TreeNode], target: int) -> Optional[TreeNode]:  # O(n) time, O(h) space
        # Post-order: process children first, then check if current node became a leaf with target value
        if not root:
            return None
        root.left = self.removeLeafNodes(root.left, target)
        root.right = self.removeLeafNodes(root.right, target)
        if not root.left and not root.right and root.val == target:
            return None
        return root


if __name__ == '__main__':
    s = Solution()

    root1 = build_tree([1, 2, 3, 2, None, 2, 4])
    assert tree_to_list(s.removeLeafNodes(root1, 2)) == [1, None, 3, None, 4]

    root2 = build_tree([1, 3, 3, 3, 2])
    assert tree_to_list(s.removeLeafNodes(root2, 3)) == [1, 3, None, None, 2]

    root3 = build_tree([1, 2, None, 2, None, 2])
    assert tree_to_list(s.removeLeafNodes(root3, 2)) == [1]

    print("All tests passed.")
Delete Node In Bst β–Ό expand
"""
# LC 450: Delete Node in a BST

Delete the node with a given key from a BST and return the (possibly new) root while
keeping the BST property intact.

## Examples
```text
Input:  root = [5, 3, 6, 2, 4, null, 7], key = 3
Output: [5, 4, 6, 2, null, null, 7]

Input:  root = [5, 3, 6, 2, 4, null, 7], key = 0
Output: [5, 3, 6, 2, 4, null, 7]

Input:  root = [], key = 0
Output: []
```

## Constraints
- The number of nodes in the tree is in the range [0, 10^4].
- -10^5 <= Node.val <= 10^5
- Each node has a unique value; root is a valid BST.
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_list(root: Optional[TreeNode]) -> List:
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        if node:
            result.append(node.val)
            queue.append(node.left)
            queue.append(node.right)
        else:
            result.append(None)
    while result and result[-1] is None:
        result.pop()
    return result


class Solution:
    """
    ## 450. Delete Node in a BST

    Given a root node reference of a BST and a key, delete the node with the given key in the BST.
    Return the root node reference (possibly updated) of the BST.

    The deletion can be divided into two stages:
    1. Search for the node to remove.
    2. If the node is found, delete the node.

    ### Examples

    ```text
    Example 1:
    Input:  root = [5,3,6,2,4,null,7], key = 3
    Output: [5,4,6,2,null,null,7]

    Example 2:
    Input:  root = [5,3,6,2,4,null,7], key = 0
    Output: [5,3,6,2,4,null,7]

    Example 3:
    Input:  root = [], key = 0
    Output: []
    ```

    ### Constraints
    - The number of nodes in the tree is in the range `[0, 10^4]`.
    - `-10^5 <= Node.val <= 10^5`
    - Each node has a unique value.
    - `root` is a valid binary search tree.
    - `-10^5 <= key <= 10^5`
    """

    def _find_min(self, node: TreeNode) -> TreeNode:
        while node.left:
            node = node.left
        return node

    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        # Find node: go left/right based on key comparison
        # On deletion: no children -> None; one child -> return it
        # Two children: replace with in-order successor (leftmost of right subtree)
        # Time: O(h), Space: O(h) β€” find node then replace with in-order successor
        if not root:
            return None
        if key < root.val:
            root.left = self.deleteNode(root.left, key)
        elif key > root.val:
            root.right = self.deleteNode(root.right, key)
        else:
            if not root.left:
                return root.right
            if not root.right:
                return root.left
            successor = self._find_min(root.right)
            root.val = successor.val
            root.right = self.deleteNode(root.right, successor.val)
        return root


if __name__ == '__main__':
    sol = Solution()

    root = build_tree([5, 3, 6, 2, 4, None, 7])
    result = sol.deleteNode(root, 3)
    vals = tree_to_list(result)
    assert 3 not in vals
    assert 5 in vals and 2 in vals and 4 in vals

    root2 = build_tree([5, 3, 6, 2, 4, None, 7])
    result2 = sol.deleteNode(root2, 0)
    assert tree_to_list(result2) == tree_to_list(build_tree([5, 3, 6, 2, 4, None, 7]))

    assert sol.deleteNode(None, 0) is None

    print("All tests passed.")
Diameter Of Binary Tree β–Ό expand
"""
# LC 543: Diameter of Binary Tree

Return the length of the longest path between any two nodes in a binary tree. The path
may or may not pass through the root. Length is measured as the number of edges.

## Examples
```text
Input:  root = [1, 2, 3, 4, 5]
Output: 3

Input:  root = [1, 2]
Output: 1
```

## Constraints
- The number of nodes in the tree is in the range [1, 10^4].
- -100 <= Node.val <= 100
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 543. Diameter of Binary Tree

    Given the `root` of a binary tree, return the length of the diameter of the tree.

    The diameter of a binary tree is the length of the longest path between any two nodes
    in a tree. This path may or may not pass through the root.

    The length of a path between two nodes is represented by the number of edges between them.

    ### Examples

    ```text
    Input:  root = [1, 2, 3, 4, 5]
    Output: 3
    Explanation: 3 is the length of the path [4,2,1,3] or [5,2,1,3].

    Input:  root = [1, 2]
    Output: 1
    ```

    ### Constraints

    - The number of nodes in the tree is in the range `[1, 10^4]`.
    - `-100 <= Node.val <= 100`
    """

    def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
        # For each node, diameter through it = left_height + right_height
        # Track global max; return height (1 + max child height) up the recursion
        # Time: O(n), Space: O(n)
        self.diameter = 0

        def depth(node: Optional[TreeNode]) -> int:
            if not node:
                return 0
            left = depth(node.left)
            right = depth(node.right)
            self.diameter = max(self.diameter, left + right)
            return 1 + max(left, right)

        depth(root)
        return self.diameter


if __name__ == '__main__':
    s = Solution()

    assert s.diameterOfBinaryTree(build_tree([1, 2, 3, 4, 5])) == 3
    assert s.diameterOfBinaryTree(build_tree([1, 2])) == 1
    assert s.diameterOfBinaryTree(build_tree([1])) == 0

    print("All tests passed.")
Flatten Binary Tree To Linked List β–Ό expand
"""
LC 114 - Flatten Binary Tree to Linked List

Problem: Flatten a binary tree in-place into a linked list in preorder.
Use the right pointer; set all left pointers to None.

Example:
    Input:  [1,2,5,3,4,null,6]
    Output: [1,null,2,null,3,null,4,null,5,null,6]

Constraints:
    - Number of nodes: [0, 2000]
    - -100 <= Node.val <= 100
    - Must be done in-place (O(1) extra space preferred)
"""
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i]); q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i]); q.append(node.right)
        i += 1
    return root


class Solution:
    def flatten(self, root: Optional[TreeNode]) -> None:
        # Approach 1: reverse postorder (right β†’ left β†’ root).
        # Track previously visited node; attach it as current's right child β€” O(n)
        self.prev = None

        def dfs(node):
            if not node: return
            dfs(node.right)
            dfs(node.left)
            node.right = self.prev   # attach previously processed node
            node.left  = None
            self.prev  = node

        dfs(root)

    def flatten_iterative(self, root: Optional[TreeNode]) -> None:
        # Approach 2: Morris-like iterative β€” O(n) time, O(1) space.
        # For each node with a left child, find the rightmost node of left subtree
        # and connect it to current's right; then move left subtree to right.
        curr = root
        while curr:
            if curr.left:
                # find rightmost node of left subtree
                rightmost = curr.left
                while rightmost.right:
                    rightmost = rightmost.right
                rightmost.right = curr.right  # connect to original right subtree
                curr.right = curr.left        # move left subtree to right
                curr.left  = None
            curr = curr.right


if __name__ == "__main__":
    def to_list(root):
        res = []
        while root:
            res.append(root.val)
            assert root.left is None
            root = root.right
        return res

    sol = Solution()

    root = build_tree([1, 2, 5, 3, 4, None, 6])
    sol.flatten(root)
    assert to_list(root) == [1, 2, 3, 4, 5, 6]

    root2 = build_tree([1, 2, 5, 3, 4, None, 6])
    sol.flatten_iterative(root2)
    assert to_list(root2) == [1, 2, 3, 4, 5, 6]

    print("All tests passed.")
Floor And Ceil In Bst β–Ό expand
"""
# Floor and Ceil in a BST

Given a BST and a target key, find the floor (largest value <= key) and the ceil
(smallest value >= key). Return -1 when no floor/ceil exists.

## Examples
```text
Input:  BST = [8, 4, 12, 2, 6, 10, 14], key = 5
Output: floor = 4, ceil = 6

Input:  BST = [8, 4, 12, 2, 6, 10, 14], key = 8
Output: floor = 8, ceil = 8
```

## Constraints
- The tree is a valid BST.
- Floor/ceil return -1 when no qualifying value exists.
"""
from __future__ import annotations
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(values: list) -> Optional[TreeNode]:
    if not values or values[0] is None:
        return None
    root = TreeNode(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if i < len(values) and values[i] is not None:
            node.left = TreeNode(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = TreeNode(values[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    def floor(self, root: Optional[TreeNode], key: int) -> int:
        """Largest value <= key. Returns -1 if no floor exists."""
        floor_val = -1
        while root:
            if root.val == key:
                return root.val              # exact match is the floor
            elif root.val < key:
                floor_val = root.val         # current is a candidate; go right for closer value
                root = root.right
            else:
                root = root.left             # current too large; go left
        return floor_val

    def ceil(self, root: Optional[TreeNode], key: int) -> int:
        """Smallest value >= key. Returns -1 if no ceil exists."""
        ceil_val = -1
        while root:
            if root.val == key:
                return root.val              # exact match is the ceil
            elif root.val > key:
                ceil_val = root.val          # current is a candidate; go left for closer value
                root = root.left
            else:
                root = root.right            # current too small; go right
        return ceil_val


# ---------- Tests ----------
if __name__ == "__main__":
    sol = Solution()

    # BST: [8,4,12,2,6,10,14]
    root = build_tree([8, 4, 12, 2, 6, 10, 14])

    # Test 1: key between two nodes
    assert sol.floor(root, 5) == 4,  f"Expected 4, got {sol.floor(root, 5)}"
    assert sol.ceil(root, 5) == 6,   f"Expected 6, got {sol.ceil(root, 5)}"

    # Test 2: key exactly in tree
    assert sol.floor(root, 6) == 6
    assert sol.ceil(root, 6) == 6

    # Test 3: key smaller than all nodes
    assert sol.floor(root, 1) == -1
    assert sol.ceil(root, 1) == 2

    # Test 4: key larger than all nodes
    assert sol.floor(root, 15) == 14
    assert sol.ceil(root, 15) == -1

    # Test 5: empty tree
    assert sol.floor(None, 5) == -1
    assert sol.ceil(None, 5) == -1

    print("All tests passed!")
House Robber Iii β–Ό expand
"""
# LC 337: House Robber III

Houses are arranged in a binary tree. Adjacent (directly linked) houses cannot both be
robbed. Return the maximum amount that can be robbed without alerting the police.

## Examples
```text
Input:  root = [3, 2, 3, null, 3, null, 1]
Output: 7

Input:  root = [3, 4, 5, 1, 3, null, 1]
Output: 9
```

## Constraints
- The number of nodes in the tree is in the range [1, 10^4].
- 0 <= Node.val <= 10^4
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 337. House Robber III

    The thief has found himself a new place for his thievery again. There is only one entrance
    to this area, called `root`.

    Besides the root, each house has one and only one parent house. After a tour, the smart
    thief realized that all houses in this place form a binary tree. It will automatically
    contact the police if **two directly-linked houses were broken into on the same night**.

    Given the `root` of the binary tree, return the maximum amount of money the thief can rob
    **without alerting the police**.

    ---

    ### Examples

    ```text
    Example 1:
        Input:  root = [3, 2, 3, null, 3, null, 1]
        Output: 7
        Explanation: Rob node 3 (root) + node 3 (left-right) + node 1 (right-right) = 3 + 3 + 1 = 7.

    Example 2:
        Input:  root = [3, 4, 5, 1, 3, null, 1]
        Output: 9
        Explanation: Rob node 4 (left) + node 5 (right) = 4 + 5 = 9.
    ```

    ---

    ### Constraints

    - The number of nodes in the tree is in the range [1, 10^4].
    - 0 <= Node.val <= 10^4
    """

    def rob(self, root: Optional[TreeNode]) -> int:  # O(n) time, O(n) space
        # For each node return (rob_this, skip_this) pair
        # rob_this = node.val + skip_left + skip_right
        # skip_this = max(rob_left, skip_left) + max(rob_right, skip_right)
        memo = {}

        def dfs(node):
            if not node:
                return 0
            if node in memo:
                return memo[node]
            # rob this node: skip children, take grandchildren
            rob_this = node.val
            if node.left:
                rob_this += dfs(node.left.left) + dfs(node.left.right)
            if node.right:
                rob_this += dfs(node.right.left) + dfs(node.right.right)
            skip_this = dfs(node.left) + dfs(node.right)
            memo[node] = max(rob_this, skip_this)
            return memo[node]

        return dfs(root)

    def rob_pair(self, root: Optional[TreeNode]) -> int:  # O(n) time, O(h) space
        def dfs(node):
            # returns (max if rob node, max if skip node)
            if not node:
                return (0, 0)
            left_rob, left_skip = dfs(node.left)
            right_rob, right_skip = dfs(node.right)
            rob_this = node.val + left_skip + right_skip
            skip_this = max(left_rob, left_skip) + max(right_rob, right_skip)
            return (rob_this, skip_this)

        return max(dfs(root))


if __name__ == '__main__':
    s = Solution()

    root1 = build_tree([3, 2, 3, None, 3, None, 1])
    assert s.rob(root1) == 7
    assert s.rob_pair(build_tree([3, 2, 3, None, 3, None, 1])) == 7

    root2 = build_tree([3, 4, 5, 1, 3, None, 1])
    assert s.rob(root2) == 9
    assert s.rob_pair(build_tree([3, 4, 5, 1, 3, None, 1])) == 9

    print("All tests passed.")
Inorder Successor Predecessor Bst β–Ό expand
"""
# LC 285 / 510: Inorder Successor and Predecessor in BST

Given a BST and a node `p`, find the inorder successor (smallest value greater than
`p.val`) and the inorder predecessor (largest value less than `p.val`). Return None
when no such node exists.

## Examples
```text
Input:  BST = [5, 3, 6, 2, 4, null, null, 1], p = 3
Output: successor = 4, predecessor = 2

Input:  BST = [5, 3, 6, 2, 4, null, null, 1], p = 6
Output: successor = None, predecessor = 5
```

## Constraints
- The tree is a valid BST.
- Returns None when the successor/predecessor does not exist.
"""
from __future__ import annotations
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(values: list) -> Optional[TreeNode]:
    if not values or values[0] is None:
        return None
    root = TreeNode(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if i < len(values) and values[i] is not None:
            node.left = TreeNode(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = TreeNode(values[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    # --- Approach 1: Inorder traversal O(n) ---
    def _inorder(self, root):
        result = []
        def dfs(node):
            if node:
                dfs(node.left)
                result.append(node)
                dfs(node.right)
        dfs(root)
        return result

    def inorderSuccessor_naive(self, root: Optional[TreeNode], p: TreeNode) -> Optional[TreeNode]:
        nodes = self._inorder(root)
        for i, node in enumerate(nodes):
            if node.val == p.val and i + 1 < len(nodes):
                return nodes[i + 1]
        return None

    def inorderPredecessor_naive(self, root: Optional[TreeNode], p: TreeNode) -> Optional[TreeNode]:
        nodes = self._inorder(root)
        for i, node in enumerate(nodes):
            if node.val == p.val and i - 1 >= 0:
                return nodes[i - 1]
        return None

    # --- Approach 2: BST property O(h) ---
    def inorderSuccessor(self, root: Optional[TreeNode], p: TreeNode) -> Optional[TreeNode]:
        successor = None
        while root:
            if p.val < root.val:
                successor = root             # root could be successor; go left for closer one
                root = root.left
            else:
                root = root.right            # p is >= root; successor must be in right subtree
        return successor

    def inorderPredecessor(self, root: Optional[TreeNode], p: TreeNode) -> Optional[TreeNode]:
        predecessor = None
        while root:
            if p.val > root.val:
                predecessor = root           # root could be predecessor; go right for closer one
                root = root.right
            else:
                root = root.left             # p is <= root; predecessor must be in left subtree
        return predecessor


# ---------- Tests ----------
if __name__ == "__main__":
    sol = Solution()

    # BST: [5,3,6,2,4,null,null,1]
    root = build_tree([5, 3, 6, 2, 4, None, None, 1])

    p3 = TreeNode(3)
    succ = sol.inorderSuccessor(root, p3)
    pred = sol.inorderPredecessor(root, p3)
    assert succ is not None and succ.val == 4,  f"Expected successor=4, got {succ}"
    assert pred is not None and pred.val == 2,  f"Expected predecessor=2, got {pred}"

    # Test: successor of max node
    p6 = TreeNode(6)
    assert sol.inorderSuccessor(root, p6) is None

    # Test: predecessor of min node
    p1 = TreeNode(1)
    assert sol.inorderPredecessor(root, p1) is None

    # Test: root node
    p5 = TreeNode(5)
    assert sol.inorderSuccessor(root, p5).val == 6
    assert sol.inorderPredecessor(root, p5).val == 4

    # Verify naive approach matches
    assert sol.inorderSuccessor_naive(root, p3).val == 4
    assert sol.inorderPredecessor_naive(root, p3).val == 2

    print("All tests passed!")
Insert Into Bst β–Ό expand
"""
# LC 701: Insert into a Binary Search Tree

Insert a value into a BST and return the root. The value is guaranteed not to already
exist in the tree; any valid BST resulting from the insertion is accepted.

## Examples
```text
Input:  root = [4, 2, 7, 1, 3], val = 5
Output: [4, 2, 7, 1, 3, 5]

Input:  root = [40, 20, 60, 10, 30, 50, 70], val = 25
Output: [40, 20, 60, 10, 30, 50, 70, null, null, 25]
```

## Constraints
- The number of nodes in the tree is in the range [0, 10^4].
- -10^8 <= Node.val <= 10^8
- val does not exist in the original BST.
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_list(root: Optional[TreeNode]) -> List:
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        if node:
            result.append(node.val)
            queue.append(node.left)
            queue.append(node.right)
        else:
            result.append(None)
    while result and result[-1] is None:
        result.pop()
    return result


class Solution:
    """
    ## 701. Insert into a Binary Search Tree

    You are given the `root` node of a BST and a `val` to insert into the tree.
    Return the root node of the BST after the insertion. It is guaranteed that the new value
    does not exist in the original BST.

    There may exist multiple valid ways for the insertion, as long as the tree remains a BST
    after insertion. You can return any of them.

    ### Examples

    ```text
    Example 1:
    Input:  root = [4,2,7,1,3], val = 5
    Output: [4,2,7,1,3,5]

    Example 2:
    Input:  root = [40,20,60,10,30,50,70], val = 25
    Output: [40,20,60,10,30,50,70,null,null,25]

    Example 3:
    Input:  root = [], val = 5
    Output: [5]
    ```

    ### Constraints
    - The number of nodes in the tree is in the range `[0, 10^4]`.
    - `-10^8 <= Node.val <= 10^8`
    - All the values `Node.val` are unique.
    - `-10^8 <= val <= 10^8`
    - It's guaranteed that `val` does not exist in the original BST.
    """

    def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        # Navigate left if val < root.val, right if val > root.val
        # Insert at the first None position found
        # Time: O(h), Space: O(h) β€” recurse to the correct leaf position
        if not root:
            return TreeNode(val)
        if val < root.val:
            root.left = self.insertIntoBST(root.left, val)
        else:
            root.right = self.insertIntoBST(root.right, val)
        return root

    def insertIntoBST_iterative(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        # Time: O(h), Space: O(1) β€” walk to insertion point without recursion
        new_node = TreeNode(val)
        if not root:
            return new_node
        cur = root
        while True:
            if val < cur.val:
                if not cur.left:
                    cur.left = new_node
                    break
                cur = cur.left
            else:
                if not cur.right:
                    cur.right = new_node
                    break
                cur = cur.right
        return root


if __name__ == '__main__':
    sol = Solution()

    root = build_tree([4, 2, 7, 1, 3])
    result = sol.insertIntoBST(root, 5)
    assert 5 in tree_to_list(result)

    root2 = build_tree([4, 2, 7, 1, 3])
    result2 = sol.insertIntoBST_iterative(root2, 5)
    assert 5 in tree_to_list(result2)

    assert sol.insertIntoBST(None, 5).val == 5

    print("All tests passed.")
Invert Binary Tree β–Ό expand
"""
# LC 226: Invert Binary Tree

Invert a binary tree (mirror it) and return its root.

## Examples
```text
Input:  root = [4, 2, 7, 1, 3, 6, 9]
Output: [4, 7, 2, 9, 6, 3, 1]

Input:  root = [2, 1, 3]
Output: [2, 3, 1]

Input:  root = []
Output: []
```

## Constraints
- The number of nodes in the tree is in the range [0, 100].
- -100 <= Node.val <= 100
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_list(root: Optional[TreeNode]) -> List:
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        if node:
            result.append(node.val)
            queue.append(node.left)
            queue.append(node.right)
        else:
            result.append(None)
    while result and result[-1] is None:
        result.pop()
    return result


class Solution:
    """
    ## 226. Invert Binary Tree

    Given the `root` of a binary tree, invert the tree, and return its root.

    ### Examples

    ```text
    Input:  root = [4, 2, 7, 1, 3, 6, 9]
    Output: [4, 7, 2, 9, 6, 3, 1]

    Input:  root = [2, 1, 3]
    Output: [2, 3, 1]

    Input:  root = []
    Output: []
    ```

    ### Constraints

    - The number of nodes in the tree is in the range `[0, 100]`.
    - `-100 <= Node.val <= 100`
    """

    def invertTree_recursive(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # Time: O(n), Space: O(n)
        if not root:
            return None
        root.left, root.right = self.invertTree_recursive(root.right), self.invertTree_recursive(root.left)
        return root

    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # Swap left and right children, then recursively invert both subtrees
        # Time: O(n), Space: O(n)
        if not root:
            return None
        queue = deque([root])
        while queue:
            node = queue.popleft()
            node.left, node.right = node.right, node.left
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        return root


if __name__ == '__main__':
    s = Solution()

    assert tree_to_list(s.invertTree(build_tree([4, 2, 7, 1, 3, 6, 9]))) == [4, 7, 2, 9, 6, 3, 1]
    assert tree_to_list(s.invertTree_recursive(build_tree([2, 1, 3]))) == [2, 3, 1]
    assert s.invertTree(None) is None

    print("All tests passed.")
Kth Smallest Element In Bst β–Ό expand
"""
# LC 230: Kth Smallest Element in a BST

Return the k-th smallest value (1-indexed) in a BST.

## Examples
```text
Input:  root = [3, 1, 4, null, 2], k = 1
Output: 1

Input:  root = [5, 3, 6, 2, 4, null, null, 1], k = 3
Output: 3
```

## Constraints
- The number of nodes in the tree is n, with 1 <= k <= n <= 10^4.
- 0 <= Node.val <= 10^4
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 230. Kth Smallest Element in a BST

    Given the `root` of a binary search tree, and an integer `k`, return the
    `k`th smallest value (1-indexed) of all the values of the nodes in the tree.

    ---

    ### Examples

    ```text
    Example 1:
        Input:  root = [3, 1, 4, null, 2], k = 1
        Output: 1

    Example 2:
        Input:  root = [5, 3, 6, 2, 4, null, null, 1], k = 3
        Output: 3
    ```

    ---

    ### Constraints

    - The number of nodes in the tree is `n`.
    - 1 <= k <= n <= 10^4
    - 0 <= Node.val <= 10^4
    """

    def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:  # O(n) time, O(n) space
        # In-order traversal of BST yields sorted order; return the k-th element
        result = []

        def inorder(node):
            if not node:
                return
            inorder(node.left)
            result.append(node.val)
            inorder(node.right)

        inorder(root)
        return result[k - 1]

    def kthSmallest_iterative(self, root: Optional[TreeNode], k: int) -> int:  # O(h+k) time, O(h) space
        stack = []
        node = root
        count = 0
        while stack or node:
            while node:
                stack.append(node)
                node = node.left
            node = stack.pop()
            count += 1
            if count == k:
                return node.val
            node = node.right
        return -1


if __name__ == '__main__':
    s = Solution()

    root1 = build_tree([3, 1, 4, None, 2])
    assert s.kthSmallest(root1, 1) == 1
    assert s.kthSmallest_iterative(build_tree([3, 1, 4, None, 2]), 1) == 1

    root2 = build_tree([5, 3, 6, 2, 4, None, None, 1])
    assert s.kthSmallest(root2, 3) == 3
    assert s.kthSmallest_iterative(build_tree([5, 3, 6, 2, 4, None, None, 1]), 3) == 3

    print("All tests passed.")
Largest Bst In Binary Tree β–Ό expand
"""
# LC 333: Largest BST Subtree

Given the root of a binary tree, find the largest subtree that is also a valid BST and
return its size (number of nodes).

## Examples
```text
Input:  root = [10, 5, 15, 1, 8, null, 7]
Output: 3

Input:  root = [4, 2, 7, 2, 3, 5, null, 2, null, null, null, null, null, 1]
Output: 2
```

## Constraints
- The number of nodes in the tree is in the range [0, 10^4].
- -10^4 <= Node.val <= 10^4
"""
from __future__ import annotations
from typing import Optional, Tuple
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(values: list) -> Optional[TreeNode]:
    if not values or values[0] is None:
        return None
    root = TreeNode(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if i < len(values) and values[i] is not None:
            node.left = TreeNode(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = TreeNode(values[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    # --- Approach 1: Check each subtree O(n^2) ---
    def _is_bst(self, node, lo=float('-inf'), hi=float('inf')) -> int:
        """Returns size of BST rooted at node, or -1 if not a valid BST."""
        if not node:
            return 0
        if not (lo < node.val < hi):
            return -1
        left = self._is_bst(node.left, lo, node.val)
        if left == -1:
            return -1
        right = self._is_bst(node.right, node.val, hi)
        if right == -1:
            return -1
        return left + right + 1

    def largestBSTSubtree_naive(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        size = self._is_bst(root)
        if size != -1:
            return size                      # whole subtree is BST
        return max(self.largestBSTSubtree_naive(root.left),
                   self.largestBSTSubtree_naive(root.right))

    # --- Approach 2: Bottom-up post-order O(n) ---
    def largestBSTSubtree(self, root: Optional[TreeNode]) -> int:
        self.max_size = 0

        def post_order(node) -> Tuple[bool, int, float, float]:
            """Returns (is_bst, size, min_val, max_val)."""
            if not node:
                return True, 0, float('inf'), float('-inf')

            l_bst, l_size, l_min, l_max = post_order(node.left)
            r_bst, r_size, r_min, r_max = post_order(node.right)

            # Current node forms a valid BST if both children are BSTs
            # and current value is greater than left max and less than right min
            if l_bst and r_bst and l_max < node.val < r_min:
                size = l_size + r_size + 1
                self.max_size = max(self.max_size, size)
                return True, size, min(l_min, node.val), max(r_max, node.val)

            # Not a valid BST; propagate invalid signal upward
            return False, 0, 0, 0

        post_order(root)
        return self.max_size


# ---------- Tests ----------
if __name__ == "__main__":
    sol = Solution()

    # Test 1: [10,5,15,1,8,null,7] -> largest BST is [5,1,8] size=3
    root = build_tree([10, 5, 15, 1, 8, None, 7])
    assert sol.largestBSTSubtree(root) == 3, f"Got {sol.largestBSTSubtree(root)}"
    assert sol.largestBSTSubtree_naive(root) == 3

    # Test 2: entire tree is a BST
    root = build_tree([4, 2, 6, 1, 3, 5, 7])
    assert sol.largestBSTSubtree(root) == 7

    # Test 3: single node
    root = build_tree([1])
    assert sol.largestBSTSubtree(root) == 1

    # Test 4: empty tree
    assert sol.largestBSTSubtree(None) == 0

    # Test 5: [3,2,4,1,null,null,5] -> entire tree is BST size=5
    root = build_tree([3, 2, 4, 1, None, None, 5])
    assert sol.largestBSTSubtree(root) == 5

    print("All tests passed!")
Lowest Common Ancestor Of Binary Tree β–Ό expand
"""
LC 236 - Lowest Common Ancestor of a Binary Tree

Problem: Find LCA of nodes p and q in a general binary tree.

Example:
    Input:  [3,5,1,6,2,0,8,null,null,7,4], p=5, q=1
    Output: 3

Constraints:
    - Number of nodes: [2, 10^5], all unique
    - p != q, both exist in the tree
"""
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i]); q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i]); q.append(node.right)
        i += 1
    return root


class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        # Approach: recursive β€” if current node is p or q return it;
        # if both subtrees return non-null, current node is the LCA β€” O(n)
        if not root or root == p or root == q:
            return root
        left  = self.lowestCommonAncestor(root.left,  p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        # both sides found a target β†’ current node is LCA
        if left and right: return root
        return left or right


if __name__ == "__main__":
    sol = Solution()
    root = build_tree([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4])

    def find(node, val):
        if not node: return None
        if node.val == val: return node
        return find(node.left, val) or find(node.right, val)

    assert sol.lowestCommonAncestor(root, find(root, 5), find(root, 1)).val == 3
    assert sol.lowestCommonAncestor(root, find(root, 5), find(root, 4)).val == 5
    print("All tests passed.")
Lowest Common Ancestor Of Bst β–Ό expand
"""
# LC 235: Lowest Common Ancestor of a Binary Search Tree

Find the lowest common ancestor (LCA) of two nodes p and q in a BST. The LCA is the
deepest node that has both p and q as descendants (a node is a descendant of itself).

## Examples
```text
Input:  root = [6, 2, 8, 0, 4, 7, 9, null, null, 3, 5], p = 2, q = 8
Output: 6

Input:  root = [6, 2, 8, 0, 4, 7, 9, null, null, 3, 5], p = 2, q = 4
Output: 2
```

## Constraints
- The number of nodes in the tree is in the range [2, 10^5].
- All Node.val are unique; p and q exist in the BST.
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


def find_node(root: TreeNode, val: int) -> Optional[TreeNode]:
    if not root:
        return None
    if root.val == val:
        return root
    return find_node(root.left, val) or find_node(root.right, val)


class Solution:
    """
    ## 235. Lowest Common Ancestor of a Binary Search Tree

    Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given
    nodes in the BST.

    The LCA is defined as the lowest node in the tree that has both `p` and `q` as descendants
    (a node can be a descendant of itself).

    ### Examples

    ```text
    Example 1:
    Input:  root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
    Output: 6

    Example 2:
    Input:  root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
    Output: 2

    Example 3:
    Input:  root = [2,1], p = 2, q = 1
    Output: 2
    ```

    ### Constraints
    - The number of nodes in the tree is in the range `[2, 10^5]`.
    - `-10^9 <= Node.val <= 10^9`
    - All `Node.val` are unique.
    - `p != q`
    - `p` and `q` will exist in the BST.
    """

    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # In BST: if both p,q < root go left; if both > root go right; else root is LCA
        # Time: O(h), Space: O(h) β€” recurse toward the split point using BST property
        if p.val < root.val and q.val < root.val:
            return self.lowestCommonAncestor(root.left, p, q)
        if p.val > root.val and q.val > root.val:
            return self.lowestCommonAncestor(root.right, p, q)
        return root

    def lowestCommonAncestor_iterative(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # Time: O(h), Space: O(1) β€” walk down without recursion stack
        node = root
        while node:
            if p.val < node.val and q.val < node.val:
                node = node.left
            elif p.val > node.val and q.val > node.val:
                node = node.right
            else:
                return node


if __name__ == '__main__':
    sol = Solution()

    root = build_tree([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5])
    p, q = find_node(root, 2), find_node(root, 8)
    assert sol.lowestCommonAncestor(root, p, q).val == 6
    assert sol.lowestCommonAncestor_iterative(root, p, q).val == 6

    p2, q2 = find_node(root, 2), find_node(root, 4)
    assert sol.lowestCommonAncestor(root, p2, q2).val == 2
    assert sol.lowestCommonAncestor_iterative(root, p2, q2).val == 2

    print("All tests passed.")
Maximum Depth Of Binary Tree β–Ό expand
"""
# LC 104: Maximum Depth of Binary Tree

Return the maximum depth (number of nodes along the longest root-to-leaf path) of a
binary tree.

## Examples
```text
Input:  root = [3, 9, 20, null, null, 15, 7]
Output: 3

Input:  root = [1, null, 2]
Output: 2
```

## Constraints
- The number of nodes in the tree is in the range [0, 10^4].
- -100 <= Node.val <= 100
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 104. Maximum Depth of Binary Tree

    Given the `root` of a binary tree, return its maximum depth.

    A binary tree's maximum depth is the number of nodes along the longest path
    from the root node down to the farthest leaf node.

    ### Examples

    ```text
    Input:  root = [3, 9, 20, null, null, 15, 7]
    Output: 3

    Input:  root = [1, null, 2]
    Output: 2
    ```

    ### Constraints

    - The number of nodes in the tree is in the range `[0, 10^4]`.
    - `-100 <= Node.val <= 100`
    """

    def maxDepth_recursive(self, root: Optional[TreeNode]) -> int:
        # Time: O(n), Space: O(n)
        if not root:
            return 0
        return 1 + max(self.maxDepth_recursive(root.left), self.maxDepth_recursive(root.right))

    def maxDepth(self, root: Optional[TreeNode]) -> int:
        # Depth = 1 + max(depth of left, depth of right); base case is None -> 0
        # Time: O(n), Space: O(n)
        if not root:
            return 0
        depth, queue = 0, deque([root])
        while queue:
            depth += 1
            for _ in range(len(queue)):
                node = queue.popleft()
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
        return depth


if __name__ == '__main__':
    s = Solution()

    assert s.maxDepth(build_tree([3, 9, 20, None, None, 15, 7])) == 3
    assert s.maxDepth_recursive(build_tree([3, 9, 20, None, None, 15, 7])) == 3

    assert s.maxDepth(build_tree([1, None, 2])) == 2
    assert s.maxDepth(None) == 0

    print("All tests passed.")
Maximum Width Of Binary Tree β–Ό expand
"""
LC 662 - Maximum Width of Binary Tree

Problem: Find the maximum width (leftmost to rightmost node, including nulls) at any level.

Example:
    Input:  [1,3,2,5,3,null,9]
    Output: 4

Constraints:
    - Number of nodes: [1, 3000]
    - -100 <= Node.val <= 100
"""
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i]); q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i]); q.append(node.right)
        i += 1
    return root


class Solution:
    def widthOfBinaryTree(self, root: Optional[TreeNode]) -> int:
        # Approach: BFS with position index (left=2*i, right=2*i+1)
        # Normalize index at each level to prevent integer overflow β€” O(n)
        if not root: return 0
        max_width = 0
        queue = deque([(root, 0)])
        while queue:
            level_len = len(queue)
            _, first_idx = queue[0]
            for _ in range(level_len):
                node, idx = queue.popleft()
                idx -= first_idx  # normalize to avoid overflow
                if node.left:  queue.append((node.left,  2 * idx))
                if node.right: queue.append((node.right, 2 * idx + 1))
            max_width = max(max_width, idx + 1)
        return max_width


if __name__ == "__main__":
    sol = Solution()
    assert sol.widthOfBinaryTree(build_tree([1, 3, 2, 5, 3, None, 9])) == 4
    assert sol.widthOfBinaryTree(build_tree([1, 3, 2, 5, None, None, 9, 6, None, 7])) == 7
    assert sol.widthOfBinaryTree(build_tree([1])) == 1
    print("All tests passed.")
Merge Two Bsts β–Ό expand
"""
# Merge Two BSTs into a Balanced BST

Given the roots of two BSTs, merge them into a single height-balanced BST containing
all elements from both trees.

## Examples
```text
Input:  BST1 = [3, 1, 5], BST2 = [4, 2, 6]
Output: balanced BST with inorder [1, 2, 3, 4, 5, 6]

Input:  BST1 = [], BST2 = [4, 2, 6]
Output: balanced BST with inorder [2, 4, 6]
```

## Constraints
- Both inputs are valid BSTs (either may be empty).
- Output must be a height-balanced BST containing every element of both inputs.
"""
from __future__ import annotations
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(values: list) -> Optional[TreeNode]:
    if not values or values[0] is None:
        return None
    root = TreeNode(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if i < len(values) and values[i] is not None:
            node.left = TreeNode(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = TreeNode(values[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_inorder(root: Optional[TreeNode]) -> list:
    result = []
    def dfs(node):
        if node:
            dfs(node.left)
            result.append(node.val)
            dfs(node.right)
    dfs(root)
    return result


class Solution:
    def _inorder(self, root: Optional[TreeNode]) -> list:
        """Extract sorted values from BST via inorder traversal."""
        result = []
        def dfs(node):
            if node:
                dfs(node.left)
                result.append(node.val)
                dfs(node.right)
        dfs(root)
        return result

    def _merge_sorted(self, a: list, b: list) -> list:
        """Merge two sorted arrays into one sorted array."""
        merged, i, j = [], 0, 0
        while i < len(a) and j < len(b):
            if a[i] <= b[j]:
                merged.append(a[i]); i += 1
            else:
                merged.append(b[j]); j += 1
        merged.extend(a[i:])
        merged.extend(b[j:])
        return merged

    def _sorted_to_bst(self, nums: list) -> Optional[TreeNode]:
        """Build a height-balanced BST from a sorted array (mid as root)."""
        if not nums:
            return None
        mid = len(nums) // 2
        node = TreeNode(nums[mid])
        node.left = self._sorted_to_bst(nums[:mid])
        node.right = self._sorted_to_bst(nums[mid + 1:])
        return node

    def mergeBSTs(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
        # Step 1: get sorted arrays from both BSTs
        sorted1 = self._inorder(root1)
        sorted2 = self._inorder(root2)
        # Step 2: merge the two sorted arrays
        merged = self._merge_sorted(sorted1, sorted2)
        # Step 3: build balanced BST from merged sorted array
        return self._sorted_to_bst(merged)


# ---------- Tests ----------
if __name__ == "__main__":
    sol = Solution()

    # Test 1: standard case
    root1 = build_tree([3, 1, 5])
    root2 = build_tree([4, 2, 6])
    merged = sol.mergeBSTs(root1, root2)
    assert tree_to_inorder(merged) == [1, 2, 3, 4, 5, 6], f"Got {tree_to_inorder(merged)}"

    # Test 2: one empty tree
    root1 = build_tree([1, None, 2])
    merged = sol.mergeBSTs(root1, None)
    assert tree_to_inorder(merged) == [1, 2]

    # Test 3: both empty
    assert sol.mergeBSTs(None, None) is None

    # Test 4: overlapping values
    root1 = build_tree([2, 1, 3])
    root2 = build_tree([2, 1, 3])
    merged = sol.mergeBSTs(root1, root2)
    assert tree_to_inorder(merged) == [1, 1, 2, 2, 3, 3]

    print("All tests passed!")
Morris Traversal β–Ό expand
"""
Morris Traversal - Inorder and Preorder (O(1) space, no stack/recursion)

Problem: Traverse a binary tree inorder and preorder using Morris threading.
Temporarily links each node to its inorder predecessor to avoid a stack.

Example:
    Input:  [1,2,3,4,5]
    Inorder:  [4,2,5,1,3]
    Preorder: [1,2,4,5,3]
"""
from typing import List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i]); q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i]); q.append(node.right)
        i += 1
    return root


class Solution:
    def morrisInorder(self, root) -> List[int]:
        # For each node with a left child, find its inorder predecessor
        # (rightmost node in left subtree).
        # If predecessor has no right child β†’ create thread, go left.
        # If predecessor's right child is current β†’ remove thread, record, go right.
        result, curr = [], root
        while curr:
            if not curr.left:
                result.append(curr.val)   # no left subtree, visit and go right
                curr = curr.right
            else:
                pred = curr.left
                while pred.right and pred.right is not curr:
                    pred = pred.right     # find inorder predecessor

                if not pred.right:
                    pred.right = curr     # create thread to come back
                    curr = curr.left
                else:
                    pred.right = None     # remove thread
                    result.append(curr.val)
                    curr = curr.right
        return result

    def morrisPreorder(self, root) -> List[int]:
        # Same threading logic but record node BEFORE going left (not after).
        result, curr = [], root
        while curr:
            if not curr.left:
                result.append(curr.val)
                curr = curr.right
            else:
                pred = curr.left
                while pred.right and pred.right is not curr:
                    pred = pred.right

                if not pred.right:
                    result.append(curr.val)  # record here (preorder = before left)
                    pred.right = curr
                    curr = curr.left
                else:
                    pred.right = None
                    curr = curr.right
        return result


if __name__ == "__main__":
    sol = Solution()
    root = build_tree([1, 2, 3, 4, 5])
    assert sol.morrisInorder(root)  == [4, 2, 5, 1, 3]
    assert sol.morrisPreorder(root) == [1, 2, 4, 5, 3]
    print("All tests passed.")
Recover Bst β–Ό expand
"""
# LC 99: Recover Binary Search Tree

Two nodes of a BST are swapped by mistake. Recover the tree without changing its
structure (fix the values in-place).

## Examples
```text
Input:  root = [1, 3, null, null, 2]
Output: [3, 1, null, null, 2]

Input:  root = [3, 1, 4, null, null, 2]
Output: [2, 1, 4, null, null, 3]
```

## Constraints
- The number of nodes in the tree is in the range [2, 1000].
- -2^31 <= Node.val <= 2^31 - 1
"""
from __future__ import annotations
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(values: list) -> Optional[TreeNode]:
    if not values or values[0] is None:
        return None
    root = TreeNode(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if i < len(values) and values[i] is not None:
            node.left = TreeNode(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = TreeNode(values[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_inorder(root: Optional[TreeNode]) -> list:
    result = []
    def dfs(node):
        if node:
            dfs(node.left)
            result.append(node.val)
            dfs(node.right)
    dfs(root)
    return result


class Solution:
    # --- Approach 1: Inorder to array, find swapped pair O(n) space ---
    def recoverTree_naive(self, root: Optional[TreeNode]) -> None:
        nodes = []
        def inorder(node):
            if node:
                inorder(node.left)
                nodes.append(node)
                inorder(node.right)
        inorder(root)

        first = second = None
        for i in range(len(nodes) - 1):
            if nodes[i].val > nodes[i + 1].val:
                second = nodes[i + 1]        # always update second
                if first is None:
                    first = nodes[i]         # only set first once

        if first and second:
            first.val, second.val = second.val, first.val

    # --- Approach 2: Morris traversal O(1) space ---
    def recoverTree(self, root: Optional[TreeNode]) -> None:
        first = second = prev = None

        curr = root
        while curr:
            if not curr.left:
                # visit curr
                if prev and prev.val > curr.val:
                    second = curr
                    if first is None:
                        first = prev
                prev = curr
                curr = curr.right
            else:
                # find inorder predecessor
                pred = curr.left
                while pred.right and pred.right is not curr:
                    pred = pred.right

                if not pred.right:
                    pred.right = curr        # create threaded link
                    curr = curr.left
                else:
                    pred.right = None        # remove threaded link
                    # visit curr
                    if prev and prev.val > curr.val:
                        second = curr
                        if first is None:
                            first = prev
                    prev = curr
                    curr = curr.right

        if first and second:
            first.val, second.val = second.val, first.val


# ---------- Tests ----------
if __name__ == "__main__":
    sol = Solution()

    # Test 1: adjacent swap [1,3,null,null,2] -> [3,1,null,null,2]
    root = build_tree([1, 3, None, None, 2])
    sol.recoverTree(root)
    assert tree_to_inorder(root) == [1, 2, 3], f"Got {tree_to_inorder(root)}"

    # Test 2: non-adjacent swap [3,1,4,null,null,2] -> [2,1,4,null,null,3]
    root = build_tree([3, 1, 4, None, None, 2])
    sol.recoverTree(root)
    assert tree_to_inorder(root) == [1, 2, 3, 4], f"Got {tree_to_inorder(root)}"

    # Test 3: naive approach
    root = build_tree([1, 3, None, None, 2])
    sol.recoverTree_naive(root)
    assert tree_to_inorder(root) == [1, 2, 3]

    print("All tests passed!")
Root To Leaf Paths β–Ό expand
"""
LC 257 - Binary Tree Paths

Problem: Return all root-to-leaf paths in a binary tree as strings joined by "->".

Example:
    Input:  [1,2,3,null,5]
    Output: ["1->2->5","1->3"]

Constraints:
    - Number of nodes: [1, 100]
    - -100 <= Node.val <= 100
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            q.append(node.right)
        i += 1
    return root


class Solution:
    def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
        # Approach: DFS with path accumulation; record on reaching a leaf β€” O(n)
        result = []

        def dfs(node, path):
            if not node.left and not node.right:  # leaf
                result.append(path)
                return
            if node.left:  dfs(node.left,  path + "->" + str(node.left.val))
            if node.right: dfs(node.right, path + "->" + str(node.right.val))

        if root:
            dfs(root, str(root.val))
        return result


if __name__ == "__main__":
    sol = Solution()

    root = build_tree([1, 2, 3, None, 5])
    assert sorted(sol.binaryTreePaths(root)) == sorted(["1->2->5", "1->3"])

    root2 = build_tree([1])
    assert sol.binaryTreePaths(root2) == ["1"]

    print("All tests passed.")
Same Tree β–Ό expand
"""
# LC 100: Same Tree

Given the roots of two binary trees, return true if they are structurally identical and
the nodes have the same values.

## Examples
```text
Input:  p = [1, 2, 3], q = [1, 2, 3]
Output: true

Input:  p = [1, 2], q = [1, null, 2]
Output: false

Input:  p = [1, 2, 1], q = [1, 1, 2]
Output: false
```

## Constraints
- The number of nodes in both trees is in the range [0, 100].
- -10^4 <= Node.val <= 10^4
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 100. Same Tree

    Given the roots of two binary trees `p` and `q`, write a function to check if they
    are the same or not.

    Two binary trees are considered the same if they are structurally identical, and the
    nodes have the same value.

    ### Examples

    ```text
    Input:  p = [1, 2, 3], q = [1, 2, 3]
    Output: true

    Input:  p = [1, 2], q = [1, null, 2]
    Output: false

    Input:  p = [1, 2, 1], q = [1, 1, 2]
    Output: false
    ```

    ### Constraints

    - The number of nodes in both trees is in the range `[0, 100]`.
    - `-10^4 <= Node.val <= 10^4`
    """

    def isSameTree_recursive(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        # Time: O(n), Space: O(n)
        if not p and not q:
            return True
        if not p or not q or p.val != q.val:
            return False
        return self.isSameTree_recursive(p.left, q.left) and self.isSameTree_recursive(p.right, q.right)

    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        # Both None: same; one None: different; values differ: different
        # Recursively check left and right subtrees
        # Time: O(n), Space: O(n)
        queue = deque([(p, q)])
        while queue:
            n1, n2 = queue.popleft()
            if not n1 and not n2:
                continue
            if not n1 or not n2 or n1.val != n2.val:
                return False
            queue.append((n1.left, n2.left))
            queue.append((n1.right, n2.right))
        return True


if __name__ == '__main__':
    s = Solution()

    assert s.isSameTree(build_tree([1, 2, 3]), build_tree([1, 2, 3])) is True
    assert s.isSameTree_recursive(build_tree([1, 2, 3]), build_tree([1, 2, 3])) is True

    assert s.isSameTree(build_tree([1, 2]), build_tree([1, None, 2])) is False
    assert s.isSameTree(build_tree([1, 2, 1]), build_tree([1, 1, 2])) is False
    assert s.isSameTree(None, None) is True

    print("All tests passed.")
Search In Bst β–Ό expand
"""
# LC 700: Search in a Binary Search Tree

Given the root of a BST and an integer `val`, return the subtree rooted at the node
whose value equals `val`. If no such node exists, return None.

## Examples
```text
Input:  root = [4, 2, 7, 1, 3], val = 2
Output: [2, 1, 3]

Input:  root = [4, 2, 7, 1, 3], val = 5
Output: []
```

## Constraints
- The number of nodes in the tree is in the range [1, 5000].
- 1 <= Node.val <= 10^7
- root is a valid BST and 1 <= val <= 10^7.
"""
from __future__ import annotations
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(values: list) -> Optional[TreeNode]:
    """Build tree from level-order list; None represents missing nodes."""
    if not values or values[0] is None:
        return None
    root = TreeNode(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if i < len(values) and values[i] is not None:
            node.left = TreeNode(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = TreeNode(values[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_list(root: Optional[TreeNode]) -> list:
    """Serialize tree to level-order list for easy comparison."""
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        if node:
            result.append(node.val)
            queue.append(node.left)
            queue.append(node.right)
        else:
            result.append(None)
    # strip trailing Nones
    while result and result[-1] is None:
        result.pop()
    return result


class Solution:
    # --- Approach 1: Recursive O(h) ---
    def searchBST_recursive(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        if not root:
            return None                      # val not found
        if root.val == val:
            return root                      # found it
        if val < root.val:
            return self.searchBST_recursive(root.left, val)   # go left
        return self.searchBST_recursive(root.right, val)      # go right

    # --- Approach 2: Iterative O(h) ---
    def searchBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
        while root:
            if root.val == val:
                return root                  # found
            elif val < root.val:
                root = root.left             # move left
            else:
                root = root.right            # move right
        return None                          # not found


# ---------- Tests ----------
if __name__ == "__main__":
    sol = Solution()

    # Test 1: val exists in tree
    root = build_tree([4, 2, 7, 1, 3])
    result = sol.searchBST(root, 2)
    assert tree_to_list(result) == [2, 1, 3], f"Expected [2,1,3], got {tree_to_list(result)}"

    # Test 2: val not in tree
    result = sol.searchBST(root, 5)
    assert result is None

    # Test 3: search root value
    result = sol.searchBST(root, 4)
    assert result is not None and result.val == 4

    # Test 4: single node
    single = build_tree([1])
    assert sol.searchBST(single, 1).val == 1
    assert sol.searchBST(single, 2) is None

    # Test 5: empty tree
    assert sol.searchBST(None, 1) is None

    # Verify recursive gives same results
    root = build_tree([4, 2, 7, 1, 3])
    assert tree_to_list(sol.searchBST_recursive(root, 2)) == [2, 1, 3]
    assert sol.searchBST_recursive(root, 5) is None

    print("All tests passed!")
Serialize And Deserialize Binary Tree β–Ό expand
"""
# LC 297: Serialize and Deserialize Binary Tree

Design an algorithm to convert a binary tree to a string and reconstruct the original
tree from that string.

## Examples
```text
Input:  root = [1, 2, 3, null, null, 4, 5]
Output: [1, 2, 3, null, null, 4, 5]

Input:  root = []
Output: []
```

## Constraints
- The number of nodes in the tree is in the range [0, 10^4].
- -1000 <= Node.val <= 1000
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


def tree_to_list(root):
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        if node:
            result.append(node.val)
            queue.append(node.left)
            queue.append(node.right)
        else:
            result.append(None)
    while result and result[-1] is None:
        result.pop()
    return result


class Codec:
    """
    ## 297. Serialize and Deserialize Binary Tree

    Serialization is the process of converting a data structure or object into a sequence of
    bits so that it can be stored in a file or memory buffer, or transmitted across a network
    connection link to be reconstructed later in the same or another computer environment.

    Design an algorithm to serialize and deserialize a binary tree. There is no restriction on
    how your serialization/deserialization algorithm should work. You just need to ensure that
    a binary tree can be serialized to a string and this string can be deserialized to the
    original tree structure.

    ---

    ### Examples

    ```text
    Example 1:
        Input:  root = [1, 2, 3, null, null, 4, 5]
        Output: [1, 2, 3, null, null, 4, 5]
        Explanation: Serialize to a string, then deserialize back to the same tree.

    Example 2:
        Input:  root = []
        Output: []
    ```

    ---

    ### Constraints

    - The number of nodes in the tree is in the range [0, 10^4].
    - -1000 <= Node.val <= 1000
    """

    # Approach 1: BFS level-order β€” O(n) time, O(n) space
    def serialize(self, root: Optional[TreeNode]) -> str:
        # BFS level-order; encode None as 'N' to preserve structure
        if not root:
            return ''
        result, queue = [], deque([root])
        while queue:
            node = queue.popleft()
            if node:
                result.append(str(node.val))
                queue.append(node.left)
                queue.append(node.right)
            else:
                result.append('null')
        return ','.join(result)

    def deserialize(self, data: str) -> Optional[TreeNode]:
        # BFS reconstruction: split tokens, assign left/right children from queue
        if not data:
            return None
        vals = data.split(',')
        root = TreeNode(int(vals[0]))
        queue = deque([root])
        i = 1
        while queue and i < len(vals):
            node = queue.popleft()
            if vals[i] != 'null':
                node.left = TreeNode(int(vals[i]))
                queue.append(node.left)
            i += 1
            if i < len(vals) and vals[i] != 'null':
                node.right = TreeNode(int(vals[i]))
                queue.append(node.right)
            i += 1
        return root


class CodecDFS:
    # Approach 2: DFS preorder β€” O(n) time, O(n) space
    def serialize(self, root: Optional[TreeNode]) -> str:
        # BFS level-order; encode None as 'N' to preserve structure
        parts = []

        def dfs(node):
            if not node:
                parts.append('null')
                return
            parts.append(str(node.val))
            dfs(node.left)
            dfs(node.right)

        dfs(root)
        return ','.join(parts)

    def deserialize(self, data: str) -> Optional[TreeNode]:
        # BFS reconstruction: split tokens, assign left/right children from queue
        vals = iter(data.split(','))

        def dfs():
            val = next(vals)
            if val == 'null':
                return None
            node = TreeNode(int(val))
            node.left = dfs()
            node.right = dfs()
            return node

        return dfs()


if __name__ == '__main__':
    codec = Codec()
    codec_dfs = CodecDFS()

    root1 = build_tree([1, 2, 3, None, None, 4, 5])
    serialized = codec.serialize(root1)
    assert tree_to_list(codec.deserialize(serialized)) == [1, 2, 3, None, None, 4, 5]

    serialized_dfs = codec_dfs.serialize(build_tree([1, 2, 3, None, None, 4, 5]))
    assert tree_to_list(codec_dfs.deserialize(serialized_dfs)) == [1, 2, 3, None, None, 4, 5]

    assert codec.serialize(None) == ''
    assert codec.deserialize('') is None

    print("All tests passed.")
Subtree Of Another Tree β–Ό expand
"""
# LC 572: Subtree of Another Tree

Return true if `subRoot` is a subtree of `root` β€” i.e., there exists a node in `root`
whose subtree is identical (structure and values) to `subRoot`.

## Examples
```text
Input:  root = [3, 4, 5, 1, 2], subRoot = [4, 1, 2]
Output: true

Input:  root = [3, 4, 5, 1, 2, null, null, null, null, 0], subRoot = [4, 1, 2]
Output: false
```

## Constraints
- The number of nodes in root is in the range [1, 2000].
- The number of nodes in subRoot is in the range [1, 1000].
- -10^4 <= Node.val <= 10^4
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 572. Subtree of Another Tree

    Given the roots of two binary trees `root` and `subRoot`, return `true` if there is a subtree
    of `root` with the same structure and node values as `subRoot`, and `false` otherwise.

    A subtree of a binary tree `tree` is a tree that consists of a node in `tree` and all of
    this node's descendants. The tree `tree` could also be considered as a subtree of itself.

    ### Examples

    ```text
    Example 1:
    Input:  root = [3,4,5,1,2], subRoot = [4,1,2]
    Output: true

    Example 2:
    Input:  root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
    Output: false
    ```

    ### Constraints
    - The number of nodes in the `root` tree is in the range `[1, 2000]`.
    - The number of nodes in the `subRoot` tree is in the range `[1, 1000]`.
    - `-10^4 <= root.val, subRoot.val <= 10^4`
    """

    def _is_same(self, s: Optional[TreeNode], t: Optional[TreeNode]) -> bool:
        if not s and not t:
            return True
        if not s or not t or s.val != t.val:
            return False
        return self._is_same(s.left, t.left) and self._is_same(s.right, t.right)

    def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
        # Check if root itself matches subRoot, or if subRoot is in either subtree
        # Time: O(m*n), Space: O(m) β€” check every node in root against subRoot
        if not root:
            return False
        if self._is_same(root, subRoot):
            return True
        return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)


if __name__ == '__main__':
    sol = Solution()

    root1 = build_tree([3, 4, 5, 1, 2])
    sub1 = build_tree([4, 1, 2])
    assert sol.isSubtree(root1, sub1) == True

    root2 = build_tree([3, 4, 5, 1, 2, None, None, None, None, 0])
    sub2 = build_tree([4, 1, 2])
    assert sol.isSubtree(root2, sub2) == False

    assert sol.isSubtree(None, build_tree([1])) == False

    print("All tests passed.")
Symmetric Tree β–Ό expand
"""
LC 101 - Symmetric Tree

Problem: Check whether a binary tree is a mirror of itself (symmetric around its center).

Example:
    Input:  [1,2,2,3,4,4,3]
    Output: True

    Input:  [1,2,2,null,3,null,3]
    Output: False

Constraints:
    - Number of nodes: [1, 1000]
    - -100 <= Node.val <= 100
"""
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            q.append(node.right)
        i += 1
    return root


class Solution:
    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        # Approach 1: Recursive β€” compare mirror positions β€” O(n)
        def is_mirror(left, right):
            if not left and not right: return True
            if not left or not right:  return False
            return (
                left.val == right.val
                and is_mirror(left.left,  right.right)  # outer pair
                and is_mirror(left.right, right.left)   # inner pair
            )
        return is_mirror(root.left, root.right)

    def isSymmetric_iterative(self, root: Optional[TreeNode]) -> bool:
        # Approach 2: Iterative BFS with queue of mirror pairs β€” O(n)
        queue = deque([(root.left, root.right)])
        while queue:
            left, right = queue.popleft()
            if not left and not right: continue
            if not left or not right:  return False
            if left.val != right.val:  return False
            queue.append((left.left,  right.right))  # outer pair
            queue.append((left.right, right.left))   # inner pair
        return True


if __name__ == "__main__":
    sol = Solution()

    root = build_tree([1, 2, 2, 3, 4, 4, 3])
    assert sol.isSymmetric(root) is True
    assert sol.isSymmetric_iterative(root) is True

    root2 = build_tree([1, 2, 2, None, 3, None, 3])
    assert sol.isSymmetric(root2) is False
    assert sol.isSymmetric_iterative(root2) is False

    print("All tests passed.")
Top View Of Binary Tree β–Ό expand
"""
GFG - Top View of Binary Tree

Problem: Return the top view of a binary tree β€” the first node visible at each
horizontal distance when viewed from above.

Example:
    Input:  [1,2,3,4,5,6,7]
    Output: [4,2,1,3,7]

Constraints:
    - Number of nodes: [1, 10^5]
    - 1 <= Node.val <= 10^5
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            q.append(node.right)
        i += 1
    return root


class Solution:
    def topView(self, root: Optional[TreeNode]) -> List[int]:
        # Approach: BFS with column tracking; keep FIRST seen per column β€” O(n)
        if not root:
            return []

        col_map = {}  # col -> first node val seen (top-most)
        queue = deque([(root, 0)])  # (node, col)
        while queue:
            node, col = queue.popleft()
            if col not in col_map:  # first time seeing this column = top view
                col_map[col] = node.val
            if node.left:  queue.append((node.left,  col - 1))
            if node.right: queue.append((node.right, col + 1))

        return [col_map[c] for c in sorted(col_map)]


if __name__ == "__main__":
    sol = Solution()

    root = build_tree([1, 2, 3, 4, 5, 6, 7])
    assert sol.topView(root) == [4, 2, 1, 3, 7]

    root2 = build_tree([1, 2, 3])
    assert sol.topView(root2) == [2, 1, 3]

    print("All tests passed.")
Two Sum Bsts β–Ό expand
"""
# LC 1214: Two Sum BSTs

Given the roots of two BSTs and an integer `target`, return true if there exists a node
in the first tree and a node in the second tree such that their values sum to `target`.

## Examples
```text
Input:  root1 = [2, 1, 4], root2 = [1, 0, 3], target = 5
Output: true

Input:  root1 = [0, -10, 10], root2 = [5, 1, 7, 0, 2], target = 18
Output: false
```

## Constraints
- The number of nodes in each tree is in the range [1, 5000].
- -10^9 <= Node.val, target <= 10^9
"""
from typing import Optional


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


# Approach 1: Inorder + Two Pointers β€” O(n+m) time, O(n+m) space
def twoSumBSTs_inorder(root1: Optional[TreeNode], root2: Optional[TreeNode], target: int) -> bool:
    def inorder(node):
        if not node:
            return []
        return inorder(node.left) + [node.val] + inorder(node.right)

    l1, l2 = inorder(root1), inorder(root2)
    i, j = 0, len(l2) - 1

    while i < len(l1) and j >= 0:
        s = l1[i] + l2[j]
        if s == target:
            return True
        elif s < target:
            i += 1
        else:
            j -= 1

    return False


# Approach 2: DFS + BST Search β€” O(nΒ·log m) time, O(h) space
def twoSumBSTs_bst(root1: Optional[TreeNode], root2: Optional[TreeNode], target: int) -> bool:
    def search(node, val):
        if not node:
            return False
        if node.val == val:
            return True
        return search(node.left, val) if val < node.val else search(node.right, val)

    def dfs(node):
        if not node:
            return False
        return search(root2, target - node.val) or dfs(node.left) or dfs(node.right)

    return dfs(root1)


if __name__ == "__main__":
    # Example 1: root1 = [2, 1, 4], root2 = [1, 0, 3], target = 5 -> True (2 + 3)
    t1 = TreeNode(2, TreeNode(1), TreeNode(4))
    t2 = TreeNode(1, TreeNode(0), TreeNode(3))
    assert twoSumBSTs_inorder(t1, t2, 5) is True
    assert twoSumBSTs_bst(t1, t2, 5) is True

    # Example 2: root1 = [0, -10, 10], root2 = [5, 1, 7, 0, 2], target = 18 -> False
    t3 = TreeNode(0, TreeNode(-10), TreeNode(10))
    t4 = TreeNode(5, TreeNode(1, TreeNode(0), TreeNode(2)), TreeNode(7))
    assert twoSumBSTs_inorder(t3, t4, 18) is False
    assert twoSumBSTs_bst(t3, t4, 18) is False

    # Single-node trees: 3 + 4 == 7
    assert twoSumBSTs_inorder(TreeNode(3), TreeNode(4), 7) is True
    assert twoSumBSTs_bst(TreeNode(3), TreeNode(4), 7) is True
    assert twoSumBSTs_inorder(TreeNode(3), TreeNode(4), 8) is False
    assert twoSumBSTs_bst(TreeNode(3), TreeNode(4), 8) is False

    print("All tests passed.")
Two Sum In Bst β–Ό expand
"""
# LC 653: Two Sum IV - Input is a BST

Given the root of a BST and an integer `k`, return true if there exist two distinct
elements in the BST whose values sum to `k`.

## Examples
```text
Input:  root = [5, 3, 6, 2, 4, null, 7], k = 9
Output: true

Input:  root = [5, 3, 6, 2, 4, null, 7], k = 28
Output: false
```

## Constraints
- The number of nodes in the tree is in the range [1, 10^4].
- -10^4 <= Node.val <= 10^4
- -10^5 <= k <= 10^5
"""
from __future__ import annotations
from typing import Optional
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(values: list) -> Optional[TreeNode]:
    if not values or values[0] is None:
        return None
    root = TreeNode(values[0])
    queue = deque([root])
    i = 1
    while queue and i < len(values):
        node = queue.popleft()
        if i < len(values) and values[i] is not None:
            node.left = TreeNode(values[i])
            queue.append(node.left)
        i += 1
        if i < len(values) and values[i] is not None:
            node.right = TreeNode(values[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    # --- Approach 1: Inorder + two pointers O(n) time, O(n) space ---
    def findTarget_two_pointer(self, root: Optional[TreeNode], k: int) -> bool:
        nums = []
        def inorder(node):
            if node:
                inorder(node.left)
                nums.append(node.val)
                inorder(node.right)
        inorder(root)                        # sorted array from BST

        lo, hi = 0, len(nums) - 1
        while lo < hi:
            s = nums[lo] + nums[hi]
            if s == k:
                return True
            elif s < k:
                lo += 1                      # need larger sum
            else:
                hi -= 1                      # need smaller sum
        return False

    # --- Approach 2: DFS + hash set O(n) time, O(n) space ---
    def findTarget(self, root: Optional[TreeNode], k: int) -> bool:
        seen = set()

        def dfs(node) -> bool:
            if not node:
                return False
            complement = k - node.val
            if complement in seen:
                return True                  # found a pair
            seen.add(node.val)
            return dfs(node.left) or dfs(node.right)

        return dfs(root)


# ---------- Tests ----------
if __name__ == "__main__":
    sol = Solution()

    # Test 1: pair exists (2+7=9)
    root = build_tree([5, 3, 6, 2, 4, None, 7])
    assert sol.findTarget(root, 9) is True
    assert sol.findTarget_two_pointer(root, 9) is True

    # Test 2: no pair
    assert sol.findTarget(root, 28) is False
    assert sol.findTarget_two_pointer(root, 28) is False

    # Test 3: k=11 (5+6)
    assert sol.findTarget(root, 11) is True

    # Test 4: single node
    single = build_tree([1])
    assert sol.findTarget(single, 2) is False

    # Test 5: two nodes that sum to k
    root2 = build_tree([1, None, 2])
    assert sol.findTarget(root2, 3) is True

    print("All tests passed!")
Validate Binary Search Tree β–Ό expand
"""
# LC 98: Validate Binary Search Tree

Determine if a binary tree is a valid BST: the left subtree contains only values
strictly less than the node, the right subtree only strictly greater, recursively.

## Examples
```text
Input:  root = [2, 1, 3]
Output: true

Input:  root = [5, 1, 4, null, null, 3, 6]
Output: false
```

## Constraints
- The number of nodes in the tree is in the range [1, 10^4].
- -2^31 <= Node.val <= 2^31 - 1
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals:
        return None
    root = TreeNode(vals[0])
    queue = deque([root])
    i = 1
    while queue and i < len(vals):
        node = queue.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            queue.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            queue.append(node.right)
        i += 1
    return root


class Solution:
    """
    ## 98. Validate Binary Search Tree

    Given the `root` of a binary tree, determine if it is a valid binary search tree (BST).

    A valid BST is defined as follows:
    - The left subtree of a node contains only nodes with keys **less than** the node's key.
    - The right subtree of a node contains only nodes with keys **greater than** the node's key.
    - Both the left and right subtrees must also be binary search trees.

    ---

    ### Examples

    ```text
    Example 1:
        Input:  root = [2, 1, 3]
        Output: true

    Example 2:
        Input:  root = [5, 1, 4, null, null, 3, 6]
        Output: false
        Explanation: The root node's value is 5, but its right child's value is 4.
    ```

    ---

    ### Constraints

    - The number of nodes in the tree is in the range [1, 10^4].
    - -2^31 <= Node.val <= 2^31 - 1
    """

    def isValidBST(self, root: Optional[TreeNode]) -> bool:  # O(n) time, O(h) space
        # Pass valid range [lo, hi] down; each node must be strictly within range
        # Going left tightens upper bound; going right tightens lower bound
        def validate(node, lo, hi):
            if not node:
                return True
            if not (lo < node.val < hi):
                return False
            return validate(node.left, lo, node.val) and validate(node.right, node.val, hi)

        return validate(root, float('-inf'), float('inf'))

    def isValidBST_inorder(self, root: Optional[TreeNode]) -> bool:  # O(n) time, O(n) space
        result = []

        def inorder(node):
            if not node:
                return
            inorder(node.left)
            result.append(node.val)
            inorder(node.right)

        inorder(root)
        return all(result[i] < result[i + 1] for i in range(len(result) - 1))


if __name__ == '__main__':
    s = Solution()

    root1 = build_tree([2, 1, 3])
    assert s.isValidBST(root1) == True
    assert s.isValidBST_inorder(build_tree([2, 1, 3])) == True

    root2 = build_tree([5, 1, 4, None, None, 3, 6])
    assert s.isValidBST(root2) == False
    assert s.isValidBST_inorder(build_tree([5, 1, 4, None, None, 3, 6])) == False

    root3 = build_tree([2, 2, 2])
    assert s.isValidBST(root3) == False

    print("All tests passed.")
Vertical Order Traversal β–Ό expand
"""
LC 987 - Vertical Order Traversal of a Binary Tree

Problem: Return vertical order traversal column by column, top to bottom.
Nodes at the same (row, col) are sorted by value.

Example:
    Input:  [3,9,20,null,null,15,7]
    Output: [[9],[3,15],[20],[7]]

Constraints:
    - Number of nodes: [1, 1000]
    - 0 <= Node.val <= 1000
"""
from typing import Optional, List
from collections import deque, defaultdict


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            q.append(node.right)
        i += 1
    return root


class Solution:
    def verticalTraversal(self, root: Optional[TreeNode]) -> List[List[int]]:
        # Approach: BFS tracking (col, row); collect all, sort, group β€” O(n log n)
        if not root:
            return []

        nodes = []  # list of (col, row, val)
        queue = deque([(root, 0, 0)])  # (node, col, row)
        while queue:
            node, col, row = queue.popleft()
            nodes.append((col, row, node.val))
            if node.left:  queue.append((node.left,  col - 1, row + 1))
            if node.right: queue.append((node.right, col + 1, row + 1))

        # sort by col, then row, then val (handles ties at same position)
        nodes.sort()

        col_map = defaultdict(list)
        for col, _, val in nodes:
            col_map[col].append(val)

        return [col_map[c] for c in sorted(col_map)]


if __name__ == "__main__":
    sol = Solution()

    root = build_tree([3, 9, 20, None, None, 15, 7])
    assert sol.verticalTraversal(root) == [[9], [3, 15], [20], [7]]

    root2 = build_tree([1, 2, 3, 4, 5, 6, 7])
    assert sol.verticalTraversal(root2) == [[4], [2], [1, 5, 6], [3], [7]]

    print("All tests passed.")
Zigzag Level Order Traversal β–Ό expand
"""
LC 103 - Binary Tree Zigzag Level Order Traversal

Problem: Return the zigzag level order traversal of a binary tree's values
(i.e., from left to right, then right to left for the next level, alternating).

Example:
    Input:  [3,9,20,null,null,15,7]
    Output: [[3],[20,9],[15,7]]

Constraints:
    - Number of nodes: [0, 2000]
    - -100 <= Node.val <= 100
"""
from typing import Optional, List
from collections import deque


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def build_tree(vals):
    if not vals: return None
    root = TreeNode(vals[0])
    q = deque([root])
    i = 1
    while q and i < len(vals):
        node = q.popleft()
        if i < len(vals) and vals[i] is not None:
            node.left = TreeNode(vals[i])
            q.append(node.left)
        i += 1
        if i < len(vals) and vals[i] is not None:
            node.right = TreeNode(vals[i])
            q.append(node.right)
        i += 1
    return root


class Solution:
    def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        # Approach: BFS with direction flag β€” O(n) time, O(n) space
        if not root:
            return []
        result = []
        queue = deque([root])
        left_to_right = True
        while queue:
            level = []
            for _ in range(len(queue)):
                node = queue.popleft()
                level.append(node.val)
                if node.left:  queue.append(node.left)
                if node.right: queue.append(node.right)
            # reverse odd levels (right-to-left direction)
            result.append(level if left_to_right else level[::-1])
            left_to_right = not left_to_right
        return result
    
    # Using deque in level to simplify and not change the BFS logic
    def zigzagLevelOrderDeque(self, root: Optional[TreeNode]) -> List[List[int]]:
            if not root:
                return []

            # Use left_to_right for clearer state tracking. Root level is left-to-right.
            left_to_right = True 
            res, queue = [], deque([root])
            
            while queue:
                level = deque() # Use a deque for the level's values
                
                for _ in range(len(queue)):
                    # ALWAYS process the main queue in standard BFS order
                    node = queue.popleft()
                    
                    # Determine where to place the value in our level list
                    if left_to_right:
                        level.append(node.val)
                    else:
                        level.appendleft(node.val)
                    
                    # ALWAYS append children in standard order (left then right)
                    if node.left:
                        queue.append(node.left)
                    if node.right:
                        queue.append(node.right)

                # Toggle the direction for the next level
                left_to_right = not left_to_right
                res.append(list(level))
            
            return res


if __name__ == "__main__":
    sol = Solution()

    root = build_tree([3, 9, 20, None, None, 15, 7])
    assert sol.zigzagLevelOrder(root) == [[3], [20, 9], [15, 7]]

    assert sol.zigzagLevelOrder(None) == []

    root2 = build_tree([1])
    assert sol.zigzagLevelOrder(root2) == [[1]]

    print("All tests passed.")

Tree Traversal

Step through traversal orders on a binary tree

3920157
Press "Start" to begin Level Order (BFS).
CurrentVisitedUnvisited