();
+ for (const node of trie.nodes) {
+ subtreeWidths.set(node.id, calculateSubtreeWidth(node.id, nodeMap));
+ }
+
+ function traverse(
+ nodeId: string,
+ level: number,
+ left: number,
+ right: number,
+ parentX?: number,
+ parentY?: number
+ ) {
+ const node = nodeMap.get(nodeId);
+ if (!node) return;
+
+ const x = (left + right) / 2;
+ const y = SVG_PADDING + level * LEVEL_HEIGHT + NODE_RADIUS;
+
+ positions.push({ node, x, y, parentX, parentY });
+
+ if (node.children.length > 0) {
+ // Calculate total children width
+ const childrenWidth = node.children.reduce(
+ (sum, childId) => sum + (subtreeWidths.get(childId) ?? MIN_NODE_SPACING),
+ 0
+ );
+
+ // Start position for first child
+ let childLeft = x - childrenWidth / 2;
+
+ for (const childId of node.children) {
+ const childWidth = subtreeWidths.get(childId) ?? MIN_NODE_SPACING;
+ traverse(
+ childId,
+ level + 1,
+ childLeft,
+ childLeft + childWidth,
+ x,
+ y
+ );
+ childLeft += childWidth;
+ }
+ }
+ }
+
+ traverse(trie.rootId, 0, 0, totalWidth);
+ return positions;
+}
+
+export function TrieView({ trie, className }: TrieViewProps) {
+ const { positions, svgWidth, svgHeight } = useMemo(() => {
+ const nodeMap = buildNodeMap(trie.nodes);
+ const depth = calculateTrieDepth(trie.rootId, nodeMap);
+ const rootWidth = calculateSubtreeWidth(trie.rootId, nodeMap);
+
+ const width = Math.max(rootWidth + SVG_PADDING * 2, 200);
+ const height = depth * LEVEL_HEIGHT + SVG_PADDING * 2;
+ const pos = calculatePositions(trie, width);
+
+ return { positions: pos, svgWidth: width, svgHeight: height };
+ }, [trie]);
+
+ // Build a set of highlighted path nodes for edge styling
+ const pathNodeIds = new Set(trie.currentPath ?? []);
+
+ return (
+
+ {trie.label && (
+
+ {trie.label}
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/visualizations-new/primitives/trie-node.tsx b/frontend/src/components/visualizations-new/primitives/trie-node.tsx
new file mode 100644
index 0000000..2c231e4
--- /dev/null
+++ b/frontend/src/components/visualizations-new/primitives/trie-node.tsx
@@ -0,0 +1,107 @@
+'use client';
+
+import { motion } from 'framer-motion';
+import { cn } from '@/lib/utils';
+import type { TrieNodeState } from '@/lib/visualizations/types';
+
+interface TrieNodeProps {
+ node: TrieNodeState;
+ x: number;
+ y: number;
+ radius?: number;
+ className?: string;
+}
+
+const STATE_CLASSES = {
+ normal: 'fill-[var(--surface-variant)] stroke-[var(--border)]',
+ current: 'fill-[var(--primary)]/20 stroke-[var(--primary)]',
+ found: 'fill-[var(--success)]/20 stroke-[var(--success)]',
+ notfound: 'fill-[var(--error)]/20 stroke-[var(--error)]',
+ highlighted: 'fill-[var(--primary)]/30 stroke-[var(--primary)]',
+ creating: 'fill-[var(--info)]/20 stroke-[var(--info)]',
+} as const;
+
+const TEXT_CLASSES = {
+ normal: 'fill-[var(--foreground)]',
+ current: 'fill-[var(--primary)]',
+ found: 'fill-[var(--success)]',
+ notfound: 'fill-[var(--error)]',
+ highlighted: 'fill-[var(--primary)]',
+ creating: 'fill-[var(--info)]',
+} as const;
+
+export function TrieNode({
+ node,
+ x,
+ y,
+ radius = 20,
+ className,
+}: TrieNodeProps) {
+ const isActive = node.state === 'current' || node.state === 'highlighted' || node.state === 'creating';
+ const isRoot = node.char === '';
+
+ return (
+
+ {/* Main circle */}
+
+
+ {/* End-of-word indicator: inner circle */}
+ {node.isEndOfWord && (
+
+ )}
+
+ {/* Character label */}
+
+ {isRoot ? '\u2205' : node.char}
+
+
+ );
+}
diff --git a/frontend/src/content/algorithms/implement-trie.ts b/frontend/src/content/algorithms/implement-trie.ts
new file mode 100644
index 0000000..007faf9
--- /dev/null
+++ b/frontend/src/content/algorithms/implement-trie.ts
@@ -0,0 +1,701 @@
+import type { AlgorithmDefinition, TrieNodeState, TrieState } from '@/lib/visualizations/types';
+
+/**
+ * Implement Trie (Prefix Tree) - LeetCode 208
+ *
+ * Trie structure after inserting "app" and "apple":
+ *
+ * (root)
+ * |
+ * a
+ * |
+ * p
+ * |
+ * p* (* = end of word)
+ * |
+ * l
+ * |
+ * e*
+ */
+
+// Node IDs
+const ROOT = 'root';
+const NODE_A = 'a';
+const NODE_P1 = 'p1';
+const NODE_P2 = 'p2';
+const NODE_L = 'l';
+const NODE_E = 'e';
+
+type NodeState = 'normal' | 'current' | 'found' | 'notfound' | 'highlighted' | 'creating';
+
+// Helper to create a single trie node
+function createNode(
+ id: string,
+ char: string,
+ state: NodeState,
+ isEndOfWord: boolean,
+ children: string[]
+): TrieNodeState {
+ return { id, char, state, isEndOfWord, children };
+}
+
+// Helper to create trie state with given nodes
+function createTrieState(
+ nodes: TrieNodeState[],
+ currentPath?: string[],
+ searchWord?: string,
+ searchIndex?: number
+): TrieState {
+ return {
+ id: 'trie',
+ nodes,
+ rootId: ROOT,
+ label: 'Trie',
+ currentPath,
+ searchWord,
+ searchIndex,
+ };
+}
+
+// Empty trie (just root)
+function emptyTrie(rootState: NodeState = 'normal'): TrieNodeState[] {
+ return [createNode(ROOT, '', rootState, false, [])];
+}
+
+// Trie with just 'a' node
+function trieWithA(
+ states: Partial> = {},
+ endOfWords: Partial> = {}
+): TrieNodeState[] {
+ return [
+ createNode(ROOT, '', states[ROOT] ?? 'normal', false, [NODE_A]),
+ createNode(NODE_A, 'a', states[NODE_A] ?? 'normal', endOfWords[NODE_A] ?? false, []),
+ ];
+}
+
+// Trie with 'a' -> 'p'
+function trieWithAP(
+ states: Partial> = {},
+ endOfWords: Partial> = {}
+): TrieNodeState[] {
+ return [
+ createNode(ROOT, '', states[ROOT] ?? 'normal', false, [NODE_A]),
+ createNode(NODE_A, 'a', states[NODE_A] ?? 'normal', endOfWords[NODE_A] ?? false, [NODE_P1]),
+ createNode(NODE_P1, 'p', states[NODE_P1] ?? 'normal', endOfWords[NODE_P1] ?? false, []),
+ ];
+}
+
+// Trie with 'a' -> 'p' -> 'p' (for "app")
+function trieWithApp(
+ states: Partial> = {},
+ endOfWords: Partial> = {}
+): TrieNodeState[] {
+ return [
+ createNode(ROOT, '', states[ROOT] ?? 'normal', false, [NODE_A]),
+ createNode(NODE_A, 'a', states[NODE_A] ?? 'normal', endOfWords[NODE_A] ?? false, [NODE_P1]),
+ createNode(NODE_P1, 'p', states[NODE_P1] ?? 'normal', endOfWords[NODE_P1] ?? false, [NODE_P2]),
+ createNode(NODE_P2, 'p', states[NODE_P2] ?? 'normal', endOfWords[NODE_P2] ?? true, []),
+ ];
+}
+
+// Trie with 'a' -> 'p' -> 'p' -> 'l' -> 'e' (for "app" and "apple")
+function trieWithAppApple(
+ states: Partial> = {},
+ endOfWords: Partial> = {}
+): TrieNodeState[] {
+ return [
+ createNode(ROOT, '', states[ROOT] ?? 'normal', false, [NODE_A]),
+ createNode(NODE_A, 'a', states[NODE_A] ?? 'normal', endOfWords[NODE_A] ?? false, [NODE_P1]),
+ createNode(NODE_P1, 'p', states[NODE_P1] ?? 'normal', endOfWords[NODE_P1] ?? false, [NODE_P2]),
+ createNode(NODE_P2, 'p', states[NODE_P2] ?? 'normal', endOfWords[NODE_P2] ?? true, [NODE_L]),
+ createNode(NODE_L, 'l', states[NODE_L] ?? 'normal', endOfWords[NODE_L] ?? false, [NODE_E]),
+ createNode(NODE_E, 'e', states[NODE_E] ?? 'normal', endOfWords[NODE_E] ?? true, []),
+ ];
+}
+
+export const implementTrieAlgorithm: AlgorithmDefinition = {
+ id: 'implement-trie',
+ title: 'Implement Trie (Prefix Tree)',
+ slug: 'implement-trie',
+ pattern: {
+ name: 'Trie',
+ description:
+ 'A tree-like data structure for efficient prefix-based operations on strings, where each node represents a character and paths from root represent words or prefixes.',
+ },
+ problemStatement:
+ 'Implement a trie with insert, search, and startsWith methods. insert(word) inserts a word into the trie. search(word) returns true if the word exists. startsWith(prefix) returns true if any word starts with the given prefix.',
+ intuition:
+ 'Think of a trie like a filing cabinet where each drawer is labeled with a letter. To find a word, you open drawers one letter at a time. Words sharing the same prefix share the same path through the drawers, making prefix operations very efficient.',
+ code: {
+ language: 'python',
+ code: `class TrieNode:
+ def __init__(self):
+ self.children = {} # char -> TrieNode
+ self.is_end = False
+
+class Trie:
+ def __init__(self):
+ self.root = TrieNode()
+
+ def insert(self, word: str) -> None:
+ node = self.root
+ for char in word:
+ if char not in node.children:
+ node.children[char] = TrieNode()
+ node = node.children[char]
+ node.is_end = True
+
+ def search(self, word: str) -> bool:
+ node = self._traverse(word)
+ return node is not None and node.is_end
+
+ def startsWith(self, prefix: str) -> bool:
+ return self._traverse(prefix) is not None
+
+ def _traverse(self, s: str) -> TrieNode:
+ node = self.root
+ for char in s:
+ if char not in node.children:
+ return None
+ node = node.children[char]
+ return node`,
+ },
+ initialExample: {
+ input: { operations: ['insert("app")', 'insert("apple")', 'search("app")', 'search("apple")', 'startsWith("ap")'] },
+ expected: [null, null, true, true, true],
+ },
+ steps: [
+ // ==========================================
+ // Phase 1: Problem (2 steps)
+ // ==========================================
+ {
+ id: 'problem-1',
+ phase: 'problem',
+ explanation:
+ 'We need to implement a Trie (prefix tree) that supports three operations: insert(word), search(word), and startsWith(prefix). Each operation should run in O(m) time where m is the length of the input string.',
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [],
+ calculations: [],
+ tries: [createTrieState(emptyTrie())],
+ },
+ },
+ {
+ id: 'problem-2',
+ phase: 'problem',
+ explanation:
+ 'We\'ll demonstrate with: insert("app"), insert("apple"), search("app"), search("apple"), and startsWith("ap"). Notice how "app" and "apple" share the prefix "app".',
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'ops', name: 'operations', value: '["app", "apple", ...]' },
+ ],
+ calculations: [],
+ tries: [createTrieState(emptyTrie('highlighted'))],
+ },
+ },
+
+ // ==========================================
+ // Phase 2: Intuition (3 steps)
+ // ==========================================
+ {
+ id: 'intuition-1',
+ phase: 'intuition',
+ explanation:
+ 'Imagine a filing cabinet where each drawer is labeled with a single letter. To store the word "app", you open drawer "a", then find drawer "p" inside it, then drawer "p" inside that. The final drawer is marked as "end of word".',
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [],
+ calculations: [],
+ tries: [createTrieState(emptyTrie())],
+ },
+ },
+ {
+ id: 'intuition-2',
+ phase: 'intuition',
+ explanation:
+ 'When adding "apple" after "app", we reuse the existing path a→p→p and only create new drawers for "l" and "e". This sharing of prefixes is what makes tries efficient for dictionary operations.',
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'insight', name: 'Key Insight', value: 'Shared prefixes = shared nodes' },
+ ],
+ calculations: [],
+ tries: [createTrieState(emptyTrie())],
+ },
+ },
+ {
+ id: 'intuition-3',
+ phase: 'intuition',
+ explanation:
+ 'To search, we follow the path character by character. If we reach the end and the final node is marked "end of word", the word exists. For startsWith, we just need to confirm the path exists—no end-of-word check needed.',
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [],
+ calculations: [],
+ tries: [createTrieState(emptyTrie())],
+ },
+ },
+
+ // ==========================================
+ // Phase 3: Pattern (2 steps)
+ // ==========================================
+ {
+ id: 'pattern-1',
+ phase: 'pattern',
+ explanation:
+ 'Each TrieNode has two properties: a dictionary mapping characters to child nodes, and a boolean is_end flag. The root node is always empty and serves as the starting point.',
+ codeLine: 1,
+ codeHighlightLines: [1, 2, 3, 4],
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'struct', name: 'TrieNode', value: '{children: {}, is_end: bool}' },
+ ],
+ calculations: [],
+ tries: [createTrieState(emptyTrie('highlighted'))],
+ },
+ },
+ {
+ id: 'pattern-2',
+ phase: 'pattern',
+ explanation:
+ 'All three operations share the same traversal pattern: start at root, follow character edges one by one. Insert creates missing nodes; search/startsWith just follow existing edges.',
+ codeLine: 11,
+ codeHighlightLines: [11, 12, 13, 14, 15, 16],
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'complexity', name: 'Time', value: 'O(m) per operation' },
+ ],
+ calculations: [],
+ tries: [createTrieState(emptyTrie())],
+ },
+ },
+
+ // ==========================================
+ // Phase 4: Code (3 steps)
+ // ==========================================
+ {
+ id: 'code-1',
+ phase: 'code',
+ explanation:
+ 'Initialize the Trie with an empty root node. This root doesn\'t represent any character—it\'s just the starting point for all operations.',
+ codeLine: 8,
+ codeHighlightLines: [6, 7, 8],
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'root', name: 'root', value: 'TrieNode()' },
+ ],
+ calculations: [],
+ tries: [createTrieState(emptyTrie('current'))],
+ },
+ },
+ {
+ id: 'code-2',
+ phase: 'code',
+ explanation:
+ 'insert(word): Traverse from root, creating new nodes for characters that don\'t exist. After processing all characters, mark the final node as end-of-word.',
+ codeLine: 11,
+ codeHighlightLines: [10, 11, 12, 13, 14, 15, 16],
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [],
+ calculations: [],
+ tries: [createTrieState(emptyTrie())],
+ },
+ },
+ {
+ id: 'code-3',
+ phase: 'code',
+ explanation:
+ 'search and startsWith both use _traverse to follow the path. search additionally checks is_end; startsWith only checks if the path exists.',
+ codeLine: 18,
+ codeHighlightLines: [18, 19, 20, 22, 23],
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [],
+ calculations: [],
+ tries: [createTrieState(emptyTrie())],
+ },
+ },
+
+ // ==========================================
+ // Phase 5: Execution - insert("app") (4 steps)
+ // ==========================================
+ {
+ id: 'exec-insert-app-1',
+ phase: 'execution',
+ explanation:
+ 'insert("app"): Start at root. Character \'a\' not in children, so create new node for \'a\'.',
+ codeLine: 13,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"app"' },
+ { id: 'char', name: 'char', value: '"a"' },
+ { id: 'node', name: 'node', value: 'root' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithA({ [ROOT]: 'current', [NODE_A]: 'creating' }),
+ [ROOT],
+ 'app',
+ 0
+ )],
+ },
+ },
+ {
+ id: 'exec-insert-app-2',
+ phase: 'execution',
+ explanation:
+ 'Move to node \'a\'. Character \'p\' not in children, create new node for first \'p\'.',
+ codeLine: 13,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"app"' },
+ { id: 'char', name: 'char', value: '"p"' },
+ { id: 'node', name: 'node', value: 'node_a' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAP({ [NODE_A]: 'current', [NODE_P1]: 'creating' }),
+ [ROOT, NODE_A],
+ 'app',
+ 1
+ )],
+ },
+ },
+ {
+ id: 'exec-insert-app-3',
+ phase: 'execution',
+ explanation:
+ 'Move to first \'p\' node. Character \'p\' not in children, create new node for second \'p\'.',
+ codeLine: 13,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"app"' },
+ { id: 'char', name: 'char', value: '"p"' },
+ { id: 'node', name: 'node', value: 'node_p1' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithApp({ [NODE_P1]: 'current', [NODE_P2]: 'creating' }, { [NODE_P2]: false }),
+ [ROOT, NODE_A, NODE_P1],
+ 'app',
+ 2
+ )],
+ },
+ },
+ {
+ id: 'exec-insert-app-4',
+ phase: 'execution',
+ explanation:
+ 'All characters processed. Mark second \'p\' as end-of-word. "app" is now in the trie!',
+ codeLine: 16,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"app"' },
+ { id: 'result', name: 'is_end', value: 'true' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithApp({ [NODE_P2]: 'found' }, { [NODE_P2]: true }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2],
+ 'app',
+ 3
+ )],
+ },
+ },
+
+ // ==========================================
+ // Phase 5: Execution - insert("apple") (3 steps)
+ // ==========================================
+ {
+ id: 'exec-insert-apple-1',
+ phase: 'execution',
+ explanation:
+ 'insert("apple"): Start at root. Traverse existing path a→p→p (nodes already exist from "app").',
+ codeLine: 15,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"apple"' },
+ { id: 'node', name: 'node', value: 'node_p2' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithApp({ [ROOT]: 'highlighted', [NODE_A]: 'highlighted', [NODE_P1]: 'highlighted', [NODE_P2]: 'current' }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2],
+ 'apple',
+ 2
+ )],
+ },
+ },
+ {
+ id: 'exec-insert-apple-2',
+ phase: 'execution',
+ explanation:
+ 'At second \'p\'. Character \'l\' not in children, create node for \'l\'. Then \'e\' not in children, create node for \'e\'.',
+ codeLine: 13,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"apple"' },
+ { id: 'char', name: 'char', value: '"l", "e"' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [NODE_P2]: 'highlighted', [NODE_L]: 'creating', [NODE_E]: 'creating' }, { [NODE_E]: false }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2],
+ 'apple',
+ 4
+ )],
+ },
+ },
+ {
+ id: 'exec-insert-apple-3',
+ phase: 'execution',
+ explanation:
+ 'All characters processed. Mark \'e\' as end-of-word. "apple" is now in the trie! Notice "app" and "apple" share nodes a→p→p.',
+ codeLine: 16,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"apple"' },
+ { id: 'result', name: 'is_end', value: 'true' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [NODE_E]: 'found' }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2, NODE_L, NODE_E],
+ 'apple',
+ 5
+ )],
+ },
+ },
+
+ // ==========================================
+ // Phase 5: Execution - search("app") (3 steps)
+ // ==========================================
+ {
+ id: 'exec-search-app-1',
+ phase: 'execution',
+ explanation:
+ 'search("app"): Traverse path a→p→p. All characters exist in the trie.',
+ codeLine: 26,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"app"' },
+ { id: 'node', name: 'node', value: 'traversing...' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [ROOT]: 'highlighted', [NODE_A]: 'highlighted', [NODE_P1]: 'highlighted', [NODE_P2]: 'current' }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2],
+ 'app',
+ 2
+ )],
+ },
+ },
+ {
+ id: 'exec-search-app-2',
+ phase: 'execution',
+ explanation:
+ 'Reached end of word. Check is_end on second \'p\': it\'s True (we marked it when inserting "app").',
+ codeLine: 19,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"app"' },
+ { id: 'is_end', name: 'is_end', value: 'true' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [NODE_P2]: 'current' }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2],
+ 'app',
+ 3
+ )],
+ },
+ },
+ {
+ id: 'exec-search-app-3',
+ phase: 'execution',
+ explanation:
+ 'search("app") returns True. The word exists in the trie.',
+ codeLine: 19,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'result', name: 'return', value: 'True' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [NODE_P2]: 'found' }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2],
+ 'app'
+ )],
+ },
+ },
+
+ // ==========================================
+ // Phase 5: Execution - search("apple") (3 steps)
+ // ==========================================
+ {
+ id: 'exec-search-apple-1',
+ phase: 'execution',
+ explanation:
+ 'search("apple"): Traverse path a→p→p→l→e. All characters exist.',
+ codeLine: 26,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"apple"' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [ROOT]: 'highlighted', [NODE_A]: 'highlighted', [NODE_P1]: 'highlighted', [NODE_P2]: 'highlighted', [NODE_L]: 'highlighted', [NODE_E]: 'current' }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2, NODE_L, NODE_E],
+ 'apple',
+ 4
+ )],
+ },
+ },
+ {
+ id: 'exec-search-apple-2',
+ phase: 'execution',
+ explanation:
+ 'Check is_end on \'e\': it\'s True (we marked it when inserting "apple").',
+ codeLine: 19,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'word', name: 'word', value: '"apple"' },
+ { id: 'is_end', name: 'is_end', value: 'true' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [NODE_E]: 'current' }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2, NODE_L, NODE_E],
+ 'apple',
+ 5
+ )],
+ },
+ },
+ {
+ id: 'exec-search-apple-3',
+ phase: 'execution',
+ explanation:
+ 'search("apple") returns True. The word exists in the trie.',
+ codeLine: 19,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'result', name: 'return', value: 'True' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [NODE_E]: 'found' }),
+ [ROOT, NODE_A, NODE_P1, NODE_P2, NODE_L, NODE_E],
+ 'apple'
+ )],
+ },
+ },
+
+ // ==========================================
+ // Phase 5: Execution - startsWith("ap") (2 steps)
+ // ==========================================
+ {
+ id: 'exec-starts-ap-1',
+ phase: 'execution',
+ explanation:
+ 'startsWith("ap"): Traverse path a→p. Both nodes exist.',
+ codeLine: 26,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'prefix', name: 'prefix', value: '"ap"' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [ROOT]: 'highlighted', [NODE_A]: 'highlighted', [NODE_P1]: 'current' }),
+ [ROOT, NODE_A, NODE_P1],
+ 'ap',
+ 1
+ )],
+ },
+ },
+ {
+ id: 'exec-starts-ap-2',
+ phase: 'execution',
+ explanation:
+ 'startsWith("ap") returns True. We found the prefix—no need to check is_end. Both "app" and "apple" start with "ap".',
+ codeLine: 23,
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'prefix', name: 'prefix', value: '"ap"' },
+ { id: 'result', name: 'return', value: 'True' },
+ ],
+ calculations: [],
+ tries: [createTrieState(
+ trieWithAppApple({ [NODE_P1]: 'found' }),
+ [ROOT, NODE_A, NODE_P1],
+ 'ap'
+ )],
+ },
+ },
+
+ // ==========================================
+ // Final Summary
+ // ==========================================
+ {
+ id: 'exec-done',
+ phase: 'execution',
+ explanation:
+ 'Complete! The trie stores "app" and "apple" efficiently by sharing the prefix a→p→p. All operations run in O(m) time where m is the string length.',
+ dataState: {
+ arrays: [],
+ pointers: [],
+ variables: [
+ { id: 'words', name: 'stored', value: '["app", "apple"]' },
+ { id: 'time', name: 'complexity', value: 'O(m) per operation' },
+ { id: 'space', name: 'space', value: 'O(total chars)' },
+ ],
+ calculations: [],
+ tries: [createTrieState(trieWithAppApple())],
+ },
+ },
+ ],
+};