diff --git a/backend/data/patterns/bfs.yaml b/backend/data/patterns/bfs.yaml new file mode 100644 index 0000000..b8c3055 --- /dev/null +++ b/backend/data/patterns/bfs.yaml @@ -0,0 +1,255 @@ +name: BFS (Breadth-First Search) +slug: bfs +difficulty_level: 3 + +description: > + Level-by-level traversal using a queue, exploring all neighbors at the current + depth before moving deeper. This guarantees the shortest path in unweighted + graphs and is ideal for problems requiring layer-by-layer processing. + +when_to_use: | + - Shortest path in unweighted graphs + - Level-order tree traversal + - Finding all nodes at distance K + - Word ladder / transformation problems + - Spreading simulations (rotting oranges, infection) + +metaphor: | + Imagine dropping a stone into a still pond. Ripples spread outward in concentric + circles—first touching everything 1 meter away, then 2 meters, then 3 meters. + BFS works the same way: it explores all nodes at distance 1 before any node at + distance 2. + + Another analogy: searching for someone in a building. You check everyone on your + current floor before taking the stairs to the next floor. You never go upstairs + until you've searched every room on the current level. + +core_concept: | + BFS uses a **queue** (FIFO) to process nodes in the order they were discovered. + This guarantees that when you first reach a node, you've taken the shortest path + to get there (in terms of number of edges). + + The key insight is that the queue naturally separates nodes by their distance + from the source. Everything added in round 1 is at distance 1. Everything added + in round 2 is at distance 2. By the time you dequeue a node, all closer nodes + have already been processed. + + **Why it finds shortest paths**: If there were a shorter path to node X, you + would have discovered X earlier (through that shorter path) and already + processed it. The first time you reach X is always via the shortest route. + +visualization: | + **Example: Shortest path from A to F** + + ``` + Graph: + A --- B --- E + | | + C --- D --- F + + BFS from A: + + Queue: [A] Visited: {A} Level 0 + Process A → add B, C + + Queue: [B, C] Visited: {A,B,C} Level 1 + Process B → add E, D + Process C → add D (skip, visited) + + Queue: [E, D] Visited: {A,B,C,E,D} Level 2 + Process E → no new neighbors + Process D → add F + + Queue: [F] Visited: {A,B,C,E,D,F} Level 3 + Process F → Found! Distance = 3 + ``` + + **Level-order tree traversal:** + + ``` + 1 + / \ + 2 3 + / \ \ + 4 5 6 + + Level 0: [1] + Level 1: [2, 3] + Level 2: [4, 5, 6] + + Result: [[1], [2,3], [4,5,6]] + ``` + +code_template: | + from collections import deque + + def bfs_shortest_path(graph: dict, start: str, end: str) -> int: + """Find shortest path distance in unweighted graph.""" + if start == end: + return 0 + + queue = deque([start]) + visited = {start} + distance = 0 + + while queue: + distance += 1 + # Process all nodes at current level + for _ in range(len(queue)): + node = queue.popleft() + + for neighbor in graph[node]: + if neighbor == end: + return distance + if neighbor not in visited: + visited.add(neighbor) + queue.append(neighbor) + + return -1 # No path found + + + def bfs_level_order(root) -> list[list]: + """Level-order traversal of binary tree.""" + 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 bfs_matrix(grid: list[list[int]], start: tuple) -> int: + """BFS on a 2D grid.""" + rows, cols = len(grid), len(grid[0]) + queue = deque([start]) + visited = {start} + directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] + distance = 0 + + while queue: + for _ in range(len(queue)): + r, c = queue.popleft() + + for dr, dc in directions: + nr, nc = r + dr, c + dc + if (0 <= nr < rows and 0 <= nc < cols + and (nr, nc) not in visited + and grid[nr][nc] != 0): # 0 = wall + visited.add((nr, nc)) + queue.append((nr, nc)) + + distance += 1 + + return distance + +recognition_signals: + - "shortest path" + - "minimum steps" + - "level order" + - "nearest" + - "unweighted graph" + - "distance" + - "spreading" + - "layer by layer" + - "all nodes at distance K" + - "word ladder" + - "rotting oranges" + +common_mistakes: + - title: Adding to visited after dequeuing instead of when enqueuing + description: | + Marking nodes as visited when you dequeue them (instead of when you + enqueue them) causes the same node to be added multiple times, leading + to incorrect distances and wasted processing. + fix: | + Always mark nodes as visited immediately when adding to the queue: + ```python + if neighbor not in visited: + visited.add(neighbor) # Mark NOW + queue.append(neighbor) + ``` + + - title: Not tracking levels correctly + description: | + Processing the entire queue without tracking level boundaries makes it + impossible to know the distance or handle level-by-level logic. + fix: | + Use the "process current level size" pattern: + ```python + for _ in range(len(queue)): # Fixed size at level start + node = queue.popleft() + # Process node + distance += 1 # Increment after level completes + ``` + + - title: Using a list instead of deque + description: | + Using `list.pop(0)` is O(n) because all elements must shift. This turns + O(V+E) BFS into O(V²). + fix: | + Use `collections.deque` which has O(1) popleft: + ```python + from collections import deque + queue = deque([start]) + ``` + + - title: Forgetting to check bounds in grid BFS + description: | + Moving to invalid coordinates (negative or beyond grid dimensions) causes + index errors. + fix: | + Always validate coordinates before accessing the grid: + ```python + if 0 <= nr < rows and 0 <= nc < cols: + # Safe to access grid[nr][nc] + ``` + +variations: + - name: Shortest path (unweighted) + description: | + Classic BFS to find minimum number of edges between two nodes. + example: "Word Ladder, Minimum Genetic Mutation, Open the Lock" + + - name: Level-order traversal + description: | + Process tree nodes level by level, returning grouped results. + example: "Binary Tree Level Order Traversal, Zigzag Level Order" + + - name: Multi-source BFS + description: | + Start BFS from multiple sources simultaneously. Useful for "spreading" + problems where multiple starting points expand in parallel. + example: "Rotting Oranges, Walls and Gates, 01 Matrix" + + - name: Bidirectional BFS + description: | + Search from both start and end simultaneously, meeting in the middle. + Reduces search space from O(b^d) to O(b^(d/2)). + example: "Word Ladder (optimized), Shortest Path in large graphs" + + - name: BFS with state + description: | + Track additional state beyond position (e.g., keys collected, walls broken). + State becomes part of the "node" being visited. + example: "Shortest Path in Grid with Obstacles Elimination" + +related_patterns: + - dfs + - tree-traversal + - matrix-traversal + +prerequisite_patterns: [] diff --git a/backend/data/patterns/dfs.yaml b/backend/data/patterns/dfs.yaml new file mode 100644 index 0000000..dd94413 --- /dev/null +++ b/backend/data/patterns/dfs.yaml @@ -0,0 +1,275 @@ +name: DFS (Depth-First Search) +slug: dfs +difficulty_level: 3 + +description: > + Explore as deep as possible along each branch before backtracking, using + recursion or an explicit stack. DFS is memory-efficient and naturally suited + for problems involving paths, connectivity, and exhaustive exploration. + +when_to_use: | + - Finding any path (not necessarily shortest) + - Detecting cycles in graphs + - Topological sorting + - Connected components + - Tree traversals (preorder, inorder, postorder) + - Exhaustive search when you need to explore all possibilities + +metaphor: | + Imagine exploring a maze by always taking the first available turn until you + hit a dead end. Then you backtrack to the last intersection and try the next + option. You keep going deeper until stuck, then retreat and try alternatives. + + Another analogy: reading a "choose your own adventure" book. You follow one + story path all the way to its ending, then go back and explore different + choices you didn't take. + +core_concept: | + DFS uses a **stack** (LIFO)—either the call stack via recursion or an explicit + stack data structure. This naturally explores depth-first: the most recently + discovered node is explored first. + + The key insight is that DFS maintains the current path from root to current + node on the stack. This makes it perfect for: + + 1. **Path problems**: The current path is always available during recursion + 2. **Cycle detection**: If you encounter a node already on the current path, + there's a cycle + 3. **Backtracking**: Naturally undo choices when returning from recursion + + Unlike BFS, DFS doesn't guarantee shortest paths, but it uses O(h) memory + (height of tree/recursion depth) vs BFS's O(w) (width of level). + +visualization: | + **Example: DFS traversal of graph** + + ``` + Graph: + A --- B --- E + | | + C --- D --- F + + DFS from A (alphabetical order): + + Visit A → explore neighbors + Visit B → explore neighbors + Visit D → explore neighbors + Visit C (already visited? skip or detect cycle) + Visit F → no unvisited neighbors, backtrack + Backtrack to D, backtrack to B + Visit E → no unvisited neighbors, backtrack + Backtrack to B, backtrack to A + Visit C → D already visited, backtrack + Done + + Order visited: A → B → D → F → E → C + ``` + + **Recursion call stack visualization:** + + ``` + dfs(A) + └─ dfs(B) + └─ dfs(D) + └─ dfs(F) ← returns + └─ dfs(C) ← already visited + └─ dfs(E) ← returns + └─ dfs(C) ← already visited + ``` + + **Cycle detection with colors:** + + ``` + WHITE (0) = unvisited + GRAY (1) = in current path (on stack) + BLACK (2) = fully processed + + If we visit a GRAY node → cycle detected! + ``` + +code_template: | + def dfs_recursive(graph: dict, start: str, visited: set = None) -> list: + """Basic recursive DFS traversal.""" + if visited is None: + visited = set() + + visited.add(start) + result = [start] + + for neighbor in graph[start]: + if neighbor not in visited: + result.extend(dfs_recursive(graph, neighbor, visited)) + + return result + + + def dfs_iterative(graph: dict, start: str) -> list: + """Iterative DFS using explicit stack.""" + visited = set() + stack = [start] + result = [] + + while stack: + node = stack.pop() + if node in visited: + continue + + visited.add(node) + result.append(node) + + # Add neighbors in reverse for same order as recursive + for neighbor in reversed(graph[node]): + if neighbor not in visited: + stack.append(neighbor) + + return result + + + def dfs_path_finding(graph: dict, start: str, end: str) -> list: + """Find a path from start to end using DFS.""" + def dfs(node: str, path: list) -> list: + if node == end: + return path + + for neighbor in graph[node]: + if neighbor not in path: # Avoid cycles + result = dfs(neighbor, path + [neighbor]) + if result: + return result + + return [] + + return dfs(start, [start]) + + + def detect_cycle(graph: dict) -> bool: + """Detect cycle in directed graph using DFS.""" + WHITE, GRAY, BLACK = 0, 1, 2 + color = {node: WHITE for node in graph} + + def dfs(node: str) -> bool: + color[node] = GRAY # Mark as being processed + + for neighbor in graph[node]: + if color[neighbor] == GRAY: # Back edge = cycle + return True + if color[neighbor] == WHITE and dfs(neighbor): + return True + + color[node] = BLACK # Mark as complete + return False + + return any(color[node] == WHITE and dfs(node) for node in graph) + + + def topological_sort(graph: dict) -> list: + """Topological sort using DFS (Kahn's algorithm alternative).""" + visited = set() + result = [] + + def dfs(node: str): + visited.add(node) + for neighbor in graph.get(node, []): + if neighbor not in visited: + dfs(neighbor) + result.append(node) # Add after all descendants + + for node in graph: + if node not in visited: + dfs(node) + + return result[::-1] # Reverse for topological order + +recognition_signals: + - "find path" + - "detect cycle" + - "connected components" + - "topological sort" + - "all paths" + - "explore all" + - "tree traversal" + - "preorder" + - "postorder" + - "inorder" + - "number of islands" + - "flood fill" + +common_mistakes: + - title: Stack overflow with deep recursion + description: | + Recursive DFS on large graphs or trees can exceed Python's default + recursion limit (usually 1000), causing a stack overflow. + fix: | + Either increase the limit with `sys.setrecursionlimit(10000)` or convert + to iterative DFS with an explicit stack. Iterative is safer for unknown + input sizes. + + - title: Marking visited too late + description: | + In iterative DFS, checking visited only when popping (not when pushing) + allows the same node to be added multiple times, wasting memory. + fix: | + For iterative DFS, either check when popping (simpler but less efficient) + or mark visited when pushing (more efficient): + ```python + if neighbor not in visited: + visited.add(neighbor) # Mark when adding + stack.append(neighbor) + ``` + + - title: Confusing visited vs current path + description: | + For cycle detection in directed graphs, using a simple visited set doesn't + distinguish between "node in current path" (cycle) and "node fully processed" + (not a cycle, just already explored). + fix: | + Use three states (WHITE/GRAY/BLACK) or track the current path separately. + A cycle exists only if you revisit a node that's still on the current path. + + - title: Wrong order in topological sort + description: | + Adding nodes to result before processing descendants gives reverse + topological order or incorrect order entirely. + fix: | + Add nodes to result only after all descendants are processed (post-order). + Then reverse the result at the end. + +variations: + - name: Tree DFS (preorder/inorder/postorder) + description: | + Visit nodes in specific orders relative to processing children. Preorder + visits parent first, inorder visits between children, postorder visits + parent last. + example: "Binary Tree Inorder Traversal, Serialize/Deserialize BST" + + - name: Graph DFS + description: | + Traverse all reachable nodes from a starting point. Need to track visited + nodes to avoid infinite loops in cyclic graphs. + example: "Number of Islands, Clone Graph, Course Schedule" + + - name: DFS with backtracking + description: | + Explore paths and undo choices when they don't lead to a solution. The + natural call stack provides the backtracking mechanism. + example: "N-Queens, Sudoku Solver, Permutations" + + - name: DFS with memoization + description: | + Cache results of DFS from each node to avoid recomputation. Transforms + DFS into dynamic programming for certain problems. + example: "Longest Increasing Path in Matrix, Word Break" + + - name: Iterative deepening DFS + description: | + Run DFS with increasing depth limits. Combines BFS's shortest-path + guarantee with DFS's memory efficiency. + example: "Finding shortest path when memory is limited" + +related_patterns: + - bfs + - backtracking + - tree-traversal + - matrix-traversal + +prerequisite_patterns: [] diff --git a/backend/data/patterns/matrix-traversal.yaml b/backend/data/patterns/matrix-traversal.yaml new file mode 100644 index 0000000..039ae4a --- /dev/null +++ b/backend/data/patterns/matrix-traversal.yaml @@ -0,0 +1,299 @@ +name: Matrix Traversal +slug: matrix-traversal +difficulty_level: 3 + +description: > + Navigate 2D grids systematically using DFS, BFS, or directional iteration. + Matrix traversal combines graph traversal concepts with coordinate-based + movement, treating each cell as a node connected to its neighbors. + +when_to_use: | + - Finding connected regions (islands) + - Shortest path in a grid + - Flood fill / painting + - Spreading simulations (rotting oranges, fire spread) + - Word search in character grid + +metaphor: | + Imagine being dropped into a maze viewed from above. You can move up, down, + left, or right, but walls block certain paths. DFS is like exploring one + corridor completely before backtracking. BFS is like sending scouts in all + directions simultaneously—whoever reaches the exit first found the shortest path. + + Another analogy: painting a floor. Flood fill (DFS/BFS) starts at one point + and spreads to all connected unpainted tiles. + +core_concept: | + A 2D grid is essentially a graph where each cell connects to its neighbors + (4-directional or 8-directional). The key adaptations from graph traversal: + + 1. **Nodes are coordinates**: Instead of node IDs, track `(row, col)` pairs + 2. **Edges are directions**: Neighbors are found by adding direction vectors + 3. **Bounds checking**: Validate coordinates before accessing the grid + 4. **In-place visited marking**: Often modify the grid itself instead of using + a separate visited set (mark '1' → '0' to indicate visited) + + **When to use DFS vs BFS:** + - **DFS**: Counting regions, checking if path exists, flood fill + - **BFS**: Shortest path, simultaneous spreading from multiple sources + +visualization: | + **Example: Number of Islands** + + ``` + Grid (1 = land, 0 = water): + + 1 1 0 0 0 + 1 1 0 0 0 + 0 0 1 0 0 + 0 0 0 1 1 + + DFS from (0,0): marks all connected 1s as visited + → Found island 1 + + Continue scanning, DFS from (2,2): + → Found island 2 + + Continue scanning, DFS from (3,3): + → Found island 3 + + Answer: 3 islands + ``` + + **Shortest path in grid (BFS):** + + ``` + Grid (0 = passable, 1 = wall): + + 0 0 0 + 1 1 0 + 0 0 0 + + Start: (0,0) End: (2,2) + + BFS levels: + Level 0: (0,0) + Level 1: (0,1) + Level 2: (0,2) + Level 3: (1,2) + Level 4: (2,2) ← Found! Distance = 4 + ``` + + **4-directional vs 8-directional:** + + ``` + 4-directional: 8-directional: + ↑ ↖ ↑ ↗ + ← ○ → ← ○ → + ↓ ↙ ↓ ↘ + ``` + +code_template: | + from collections import deque + + # Direction vectors + DIRS_4 = [(0, 1), (0, -1), (1, 0), (-1, 0)] # right, left, down, up + DIRS_8 = [(0, 1), (0, -1), (1, 0), (-1, 0), + (1, 1), (1, -1), (-1, 1), (-1, -1)] + + + def is_valid(grid: list[list], row: int, col: int) -> bool: + """Check if coordinates are within grid bounds.""" + return 0 <= row < len(grid) and 0 <= col < len(grid[0]) + + + def dfs_matrix(grid: list[list[int]], row: int, col: int) -> int: + """DFS to explore connected region, returns size.""" + if not is_valid(grid, row, col) or grid[row][col] == 0: + return 0 + + grid[row][col] = 0 # Mark as visited (modify in-place) + size = 1 + + for dr, dc in DIRS_4: + size += dfs_matrix(grid, row + dr, col + dc) + + return size + + + def count_islands(grid: list[list[str]]) -> int: + """Count connected regions of '1's.""" + if not grid: + return 0 + + count = 0 + rows, cols = len(grid), len(grid[0]) + + for r in range(rows): + for c in range(cols): + if grid[r][c] == '1': + count += 1 + dfs_sink_island(grid, r, c) + + return count + + + def dfs_sink_island(grid: list[list[str]], row: int, col: int): + """Sink an island by marking all connected '1's as '0'.""" + if not is_valid(grid, row, col) or grid[row][col] == '0': + return + + grid[row][col] = '0' # Sink this cell + + for dr, dc in DIRS_4: + dfs_sink_island(grid, row + dr, col + dc) + + + def bfs_shortest_path(grid: list[list[int]], + start: tuple, end: tuple) -> int: + """BFS to find shortest path in grid (0 = passable, 1 = wall).""" + if grid[start[0]][start[1]] == 1 or grid[end[0]][end[1]] == 1: + return -1 + + rows, cols = len(grid), len(grid[0]) + queue = deque([(*start, 0)]) # (row, col, distance) + visited = {start} + + while queue: + row, col, dist = queue.popleft() + + if (row, col) == end: + return dist + + for dr, dc in DIRS_4: + nr, nc = row + dr, col + dc + if (is_valid(grid, nr, nc) + and (nr, nc) not in visited + and grid[nr][nc] == 0): + visited.add((nr, nc)) + queue.append((nr, nc, dist + 1)) + + return -1 # No path + + + def multi_source_bfs(grid: list[list[int]]) -> int: + """BFS from multiple sources (e.g., rotting oranges).""" + rows, cols = len(grid), len(grid[0]) + queue = deque() + fresh = 0 + + # Find all sources and count targets + for r in range(rows): + for c in range(cols): + if grid[r][c] == 2: # Rotten orange + queue.append((r, c, 0)) + elif grid[r][c] == 1: # Fresh orange + fresh += 1 + + max_time = 0 + while queue: + row, col, time = queue.popleft() + max_time = max(max_time, time) + + for dr, dc in DIRS_4: + nr, nc = row + dr, col + dc + if is_valid(grid, nr, nc) and grid[nr][nc] == 1: + grid[nr][nc] = 2 # Mark as rotten + fresh -= 1 + queue.append((nr, nc, time + 1)) + + return max_time if fresh == 0 else -1 + +recognition_signals: + - "grid" + - "matrix" + - "2D array" + - "number of islands" + - "shortest path in grid" + - "flood fill" + - "surrounded regions" + - "rotting oranges" + - "word search" + - "robot path" + - "maze" + +common_mistakes: + - title: Modifying grid while using it for bounds checking + description: | + If you use grid values for validity checks (like `grid[r][c] == '1'`) + and also modify them in-place, you might check a cell you already modified. + fix: | + Modify the cell immediately upon visiting, before exploring neighbors: + ```python + grid[row][col] = '0' # Mark first + for dr, dc in DIRS_4: + dfs(grid, row + dr, col + dc) # Then explore + ``` + + - title: Forgetting to check all starting points + description: | + For problems like "number of islands," only starting DFS from (0,0) misses + islands not connected to the top-left. + fix: | + Iterate through every cell and start a new traversal from each unvisited cell: + ```python + for r in range(rows): + for c in range(cols): + if grid[r][c] == '1': + count += 1 + dfs(grid, r, c) + ``` + + - title: Index out of bounds + description: | + Accessing `grid[row][col]` without checking bounds first causes runtime + errors when exploring neighbors. + fix: | + Always check bounds before accessing: + ```python + if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == target: + # Safe to access + ``` + + - title: Using wrong direction set + description: | + Using 4-directional movement when problem requires 8-directional (diagonal) + gives wrong results, or vice versa. + fix: | + Read the problem carefully. "Adjacent" usually means 4-directional. + "Neighboring" or explicit diagonal mention means 8-directional. + +variations: + - name: Island counting + description: | + Count connected regions of the same value. DFS from each unvisited cell, + marking the entire region as visited. + example: "Number of Islands, Max Area of Island" + + - name: Shortest path in grid + description: | + BFS from start to end, counting levels. Works only if all moves have + equal cost (unweighted). + example: "Shortest Path in Binary Matrix, 01 Matrix" + + - name: Multi-source BFS + description: | + Start BFS from multiple cells simultaneously. Useful for spreading + problems where multiple sources expand in parallel. + example: "Rotting Oranges, Walls and Gates" + + - name: Word search + description: | + DFS with backtracking to find if a word exists in the grid by following + adjacent cells. Need to track the current path to avoid reusing cells. + example: "Word Search, Word Search II" + + - name: Spiral traversal + description: | + Not graph-based, but iterate through matrix in spiral order using + boundary tracking (top, bottom, left, right). + example: "Spiral Matrix, Spiral Matrix II" + +related_patterns: + - dfs + - bfs + - backtracking + +prerequisite_patterns: + - dfs + - bfs diff --git a/backend/data/patterns/tree-traversal.yaml b/backend/data/patterns/tree-traversal.yaml new file mode 100644 index 0000000..119103a --- /dev/null +++ b/backend/data/patterns/tree-traversal.yaml @@ -0,0 +1,299 @@ +name: Binary Tree Traversal +slug: tree-traversal +difficulty_level: 2 + +description: > + Visit all nodes in a binary tree in specific orders: preorder (root-left-right), + inorder (left-root-right), postorder (left-right-root), or level-order (BFS). + Each order reveals different structural information about the tree. + +when_to_use: | + - Serializing/deserializing trees + - Validating BST properties (inorder gives sorted order) + - Computing tree properties (height, size, sum) + - Copying or comparing trees + - Path sum and path finding problems + +metaphor: | + Imagine reading a family tree. **Preorder** is like announcing yourself first, + then introducing your children. **Inorder** is alphabetical—left child, then + you, then right child. **Postorder** is like calculating taxes—you need to + know your children's totals before computing your own. + + Another way to think about it: **preorder** is top-down (decisions flow from + root to leaves), **postorder** is bottom-up (results bubble from leaves to root). + +core_concept: | + The three traversal orders differ only in *when* you process the current node + relative to its children: + + - **Preorder**: Process **before** children → good for copying trees, prefix expressions + - **Inorder**: Process **between** children → BST gives sorted order + - **Postorder**: Process **after** children → good for deletion, evaluating expressions + + The key insight is that these traversals naturally map to different problems: + + - Need to see parents before children? → **Preorder** + - Need to aggregate child results? → **Postorder** + - Need sorted BST elements? → **Inorder** + - Need level-by-level? → **BFS** + +visualization: | + **Example tree:** + + ``` + 1 + / \ + 2 3 + / \ + 4 5 + ``` + + **Preorder (Root → Left → Right):** + ``` + Visit 1 → Visit 2 → Visit 4 → Visit 5 → Visit 3 + Result: [1, 2, 4, 5, 3] + ``` + + **Inorder (Left → Root → Right):** + ``` + Visit 4 → Visit 2 → Visit 5 → Visit 1 → Visit 3 + Result: [4, 2, 5, 1, 3] + + For BST: gives elements in sorted order! + ``` + + **Postorder (Left → Right → Root):** + ``` + Visit 4 → Visit 5 → Visit 2 → Visit 3 → Visit 1 + Result: [4, 5, 2, 3, 1] + + Children processed before parent—useful for computing heights/sizes. + ``` + + **Level-order (BFS):** + ``` + Level 0: [1] + Level 1: [2, 3] + Level 2: [4, 5] + Result: [1, 2, 3, 4, 5] + ``` + +code_template: | + 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 preorder_recursive(root: TreeNode) -> list: + """Preorder: Root → Left → Right""" + if not root: + return [] + return ([root.val] + + preorder_recursive(root.left) + + preorder_recursive(root.right)) + + + def preorder_iterative(root: TreeNode) -> list: + """Iterative preorder using stack.""" + if not root: + return [] + + result = [] + stack = [root] + + while stack: + node = stack.pop() + result.append(node.val) + # Push right first so left is processed first + if node.right: + stack.append(node.right) + if node.left: + stack.append(node.left) + + return result + + + def inorder_recursive(root: TreeNode) -> list: + """Inorder: Left → Root → Right""" + if not root: + return [] + return (inorder_recursive(root.left) + + [root.val] + + inorder_recursive(root.right)) + + + def inorder_iterative(root: TreeNode) -> list: + """Iterative inorder using stack.""" + result = [] + stack = [] + current = root + + while current or stack: + # Go left as far as possible + while current: + stack.append(current) + current = current.left + + # Process current node + current = stack.pop() + result.append(current.val) + + # Move to right subtree + current = current.right + + return result + + + def postorder_recursive(root: TreeNode) -> list: + """Postorder: Left → Right → Root""" + if not root: + return [] + return (postorder_recursive(root.left) + + postorder_recursive(root.right) + + [root.val]) + + + def postorder_iterative(root: TreeNode) -> list: + """Iterative postorder using two stacks.""" + if not root: + return [] + + result = [] + stack = [root] + + while stack: + node = stack.pop() + result.append(node.val) + if node.left: + stack.append(node.left) + if node.right: + stack.append(node.right) + + return result[::-1] # Reverse for postorder + + + def level_order(root: TreeNode) -> list[list]: + """Level-order traversal using BFS.""" + 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 + +recognition_signals: + - "binary tree" + - "tree traversal" + - "preorder" + - "inorder" + - "postorder" + - "level order" + - "serialize tree" + - "flatten tree" + - "BST to sorted" + - "kth smallest in BST" + - "validate BST" + - "path sum" + +common_mistakes: + - title: Stack order in iterative preorder + description: | + Pushing left child before right child processes right first because + stacks are LIFO. + fix: | + Push right child first, then left child: + ```python + if node.right: + stack.append(node.right) # Push right first + if node.left: + stack.append(node.left) # Left popped first + ``` + + - title: Iterative inorder is tricky + description: | + The iterative inorder traversal is harder to get right than preorder or + postorder because you need to track when to go left vs when to process. + fix: | + Use the "go left until null, then process and go right" pattern: + ```python + while current or stack: + while current: + stack.append(current) + current = current.left + current = stack.pop() + process(current) + current = current.right + ``` + + - title: Forgetting null checks + description: | + Accessing `node.left` or `node.right` without checking if node is None + causes attribute errors. + fix: | + Always check for null nodes first: + ```python + if not root: + return [] + ``` + + - title: Modifying tree during traversal + description: | + Changing node values or structure while traversing can cause missed nodes + or infinite loops. + fix: | + Collect nodes to modify in a separate pass, or use a traversal that + processes children before modifying the parent. + +variations: + - name: Morris Traversal + description: | + Inorder traversal using O(1) space by temporarily modifying the tree + (threading right pointers to inorder successors). + example: "Recover Binary Search Tree, Inorder without stack" + + - name: Boundary Traversal + description: | + Traverse only the boundary of the tree: left boundary, leaves, right + boundary in reverse. + example: "Boundary of Binary Tree" + + - name: Vertical Order Traversal + description: | + Group nodes by their horizontal distance from root. Nodes at same + horizontal distance are in the same vertical line. + example: "Vertical Order Traversal, Binary Tree Right Side View" + + - name: Zigzag Level Order + description: | + Level-order but alternating direction: left-to-right, then right-to-left, + and so on. + example: "Binary Tree Zigzag Level Order Traversal" + + - name: Tree Serialization + description: | + Use traversal order to convert tree to string and back. Preorder with + null markers is common. + example: "Serialize and Deserialize Binary Tree" + +related_patterns: + - dfs + - bfs + +prerequisite_patterns: []