Loading problems...
TL;DR: The optimal solution utilizes Depth-First Search (DFS) to recursively visit the left subtree, process the current node, and then visit the right subtree.
The goal of LeetCode 94 is to perform an inorder traversal on a binary tree and return a list of the node values. In an inorder traversal, the processing order for any specific node is strict: first, the entire left subtree is visited; second, the node itself is recorded; and third, the entire right subtree is visited. This is a fundamental operation in tree data structures, particularly useful when working with Binary Search Trees (BSTs) where this traversal yields sorted values.
A common naive approach to the Binary Tree Inorder Traversal problem involves implementing the recursion but mismanaging memory allocation. Specifically, a brute force implementation might create a new list for every recursive call and concatenate them at each step.
For example, in Python or Java, one might write logic that returns inorder(left) + [root.val] + inorder(right).
python
While this approach may pass the small constraints of LeetCode 94 (), it fails as a scalable solution. The overhead of list concatenation makes it inefficient for larger trees. It demonstrates a lack of understanding regarding how to maintain state (the result list) across recursive boundaries.
The core insight for solving LeetCode 94 efficiently lies in recognizing the recursive nature of the tree data structure. A binary tree is defined recursively: a tree is a node pointing to two smaller trees (left and right).
The Recursive Inorder Traversal pattern enforces a strict invariant:
u until u.left and all its descendants have been fully processed.u.right until u itself has been processed.This logic suggests a Depth-First Search (DFS) approach. By using the system call stack (via recursion), we can "pause" the processing of the current node, dive deep into the left subtree, and resume strictly when the left side is finished. We pass a single shared data structure (a list or vector) through the recursion to collect values, avoiding the concatenation cost of the brute force approach.
Visual Description:
Imagine the execution flow as a path tracing the outline of the tree. The algorithm starts at the root and immediately moves to the left child, suspending the root's processing. This continues until a leaf is reached. Upon hitting a null left child, the recursion "bounces" back to the leaf node, adds its value, and then attempts to go right. If the right child is null, it returns to the parent. The values are collected precisely at the moment the recursion returns from the left child but before it enters the right child.

The strategy relies on a helper function (or a nested function) that takes the current node and a reference to the result list.
null, stop the recursion and return immediately. This represents reaching the bottom of a branch.node.left. This ensures we go as deep left as possible before doing anything else.node.val to the result list. This happens only after the left recursive call returns.node.right. This ensures the right subtree is processed after the root.Let's trace the algorithm on a simple tree: [1, null, 2, 3] (Root is 1, Right is 2, 2's Left is 3).
dfs(1).dfs(1.left) -> dfs(null).1 to result. Result: [1].dfs(1.right) -> dfs(2).dfs(2.left) -> dfs(3).dfs(3.left) -> dfs(null). Return.3 to result. Result: [1, 3].dfs(3.right) -> dfs(null). Return.2 to result. Result: [1, 3, 2].dfs(2.right) -> dfs(null). Return.[1, 3, 2].The correctness of this approach is proved via structural induction.
null), the algorithm returns an empty list, which is the correct inorder traversal.Inorder(Left) + Root + Inorder(Right), and our algorithm executes exactly these steps in sequence, the result is correct for nodes.cpp/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: void inorderHelper(TreeNode* node, vector<int>& result) { if (node == nullptr) { return; } // Step 1: Traverse Left Subtree inorderHelper(node->left, result); // Step 2: Process Current Node result.push_back(node->val); // Step 3: Traverse Right Subtree inorderHelper(node->right, result); } vector<int> inorderTraversal(TreeNode* root) { vector<int> result; inorderHelper(root, result); return result; } };
java/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); dfs(root, result); return result; } private void dfs(TreeNode node, List<Integer> result) { if (node == null) { return; } // Step 1: Traverse Left Subtree dfs(node.left, result); // Step 2: Process Current Node result.add(node.val); // Step 3: Traverse Right Subtree dfs(node.right, result); } }
python# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: result = [] def dfs(node): if not node: return # Step 1: Traverse Left Subtree dfs(node.left) # Step 2: Process Current Node result.append(node.val) # Step 3: Traverse Right Subtree dfs(node.right) dfs(root) return result
Time Complexity: We visit every node in the binary tree exactly once. The operations performed at each node (checking for null, appending to list) are constant time . Thus, the total time is linear with respect to the number of nodes.
Space Complexity: The space complexity is determined by the recursion stack depth. In the worst case (a skewed tree resembling a linked list), the recursion depth is . In the best case (a balanced tree), the depth is . We also store elements in the result array, though typically auxiliary space refers to the stack space in this context.
The Tree DFS - Recursive Inorder Traversal pattern is highly versatile. Understanding this logic is crucial for solving:
Why do I get a StackOverflowError?
if node is null return).dfs on null pointers (or attempting to access properties of null pointers) until the system stack limit is reached.Why is my output order wrong (e.g., [Root, Left, Right])?
Why is my solution slow or using too much memory?
return dfs(left) + [val] + dfs(right)Why do I get a NullPointerException/AttributeError?
node.left or node.val before checking if node is null.null child to the function. You must check for validity before accessing members.