536. Construct Binary Tree from String 🔒
Problem Description
You are given a string representation of a binary tree and need to construct the actual binary tree from it.
The string follows these rules:
- Each node is represented by an integer value
- Child nodes (if they exist) are enclosed in parentheses immediately after their parent's value
- The first pair of parentheses after a node contains its left child
- The second pair of parentheses (if present) contains its right child
- The pattern is recursive - each child follows the same structure
For example:
"4(2(3)(1))(6(5))"
represents a tree where:- Root node has value 4
- Left child has value 2 (with its own left child 3 and right child 1)
- Right child has value 6 (with only a left child 5)
The parsing algorithm works by:
- Finding the root value (the number before any parentheses)
- Using parenthesis counting to identify where the left subtree ends
- Recursively parsing the left subtree content
- If more content exists after the left subtree, recursively parsing the right subtree
- Building the tree by connecting the parsed nodes
The solution tracks opening and closing parentheses to determine the boundaries of each subtree. When the count of (
and )
balances back to zero, we've found the end of a complete subtree expression. The first balanced section becomes the left child, and any remaining balanced section becomes the right child.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: While this problem involves a binary tree (which is a special type of graph), we need to construct it from a string representation. The tree structure with nodes and parent-child relationships makes this a graph problem.
Is it a tree?
- Yes: The problem explicitly asks us to construct a binary tree. Each node has at most two children (left and right), and there's a clear hierarchical structure with a root node.
DFS
- Yes: We arrive at DFS as the solution approach.
Conclusion: The flowchart suggests using a Depth-First Search (DFS) approach for this problem.
This makes sense because:
- We need to recursively parse nested parentheses structures in the string
- Each time we encounter a node value followed by parentheses, we need to dive deep into those parentheses to construct the subtrees before moving on
- The recursive nature of the problem (a tree contains smaller trees) naturally fits the DFS pattern
- We process each node and its entire subtree completely before backtracking to process siblings, which is the essence of depth-first traversal
The DFS approach allows us to:
- Parse the root value
- Recursively construct the left subtree from the first pair of parentheses
- Recursively construct the right subtree from the second pair of parentheses (if it exists)
- Build the tree bottom-up by connecting child nodes to their parents as we return from recursive calls
Intuition
When we look at the string representation like "4(2(3)(1))(6(5))"
, we can see a recursive pattern - the entire string follows the same structure as any substring within parentheses. This immediately suggests a recursive approach.
The key insight is that parentheses act as delimiters that define the boundaries of subtrees. Just like how we use parentheses in mathematical expressions to group operations, here they group tree nodes. Each balanced pair of parentheses contains a complete subtree that follows the exact same format as the original string.
To parse this string, we need to:
- Extract the root value (the number before any parentheses)
- Find where the left subtree ends (when parentheses balance)
- Recursively solve the same problem for what's inside those parentheses
The challenge is determining where one subtree ends and another begins. Consider "4(2(3)(1))(6(5))"
- after reading 4(
, how do we know the left subtree is 2(3)(1)
and not just 2(
? We need to count parentheses: when we've seen equal numbers of opening and closing parentheses, we've found a complete subtree.
This counting mechanism works like a stack - each (
pushes us deeper into nested structures, and each )
pops us back out. When our counter returns to zero, we know we've completely exited the current subtree.
The recursive DFS pattern emerges naturally because:
- We process the current node first (the root value)
- Then dive deep into the left child's substring
- Finally dive into the right child's substring (if it exists)
- Each recursive call handles a self-contained subtree string
This approach mirrors how we mentally parse nested structures - we identify the top-level components first, then recursively break down each component using the same rules.
Learn more about Stack, Tree, Depth-First Search and Binary Tree patterns.
Solution Approach
The implementation uses a recursive DFS function to parse the string and build the tree. Let's walk through the key components:
Base Case Handling:
The function first checks if the string is empty - if so, it returns None
since there's no node to create. This handles cases where a node has no children.
Root Value Extraction:
We find the first occurrence of (
using s.find('(')
. If no parenthesis is found (p == -1
), the entire string is just a number, so we create and return a leaf node with TreeNode(int(s))
.
If parentheses exist, we extract the root value as everything before the first (
: int(s[:p])
.
Parenthesis Counting Algorithm: The core parsing logic uses a counter-based approach:
- Initialize
cnt = 0
andstart = p
(position of first(
) - Iterate through the string from position
p
- Increment
cnt
for each(
- Decrement
cnt
for each)
- When
cnt
reaches 0, we've found a complete balanced subtree
Identifying Left and Right Subtrees:
The algorithm uses the start
variable to track which subtree we're parsing:
- When
start == p
(first timecnt
reaches 0), we've found the left subtree- Extract substring:
s[start + 1 : i]
(content between parentheses) - Recursively parse it:
root.left = dfs(s[start + 1 : i])
- Update
start = i + 1
to point past this subtree
- Extract substring:
- When
start != p
(second timecnt
reaches 0), we've found the right subtree- Extract and parse similarly:
root.right = dfs(s[start + 1 : i])
- Extract and parse similarly:
String Slicing: The solution uses Python's string slicing to extract substrings:
s[:p]
- gets the root values[start + 1 : i]
- gets content between parentheses (excluding the parentheses themselves)
Tree Construction: The tree is built bottom-up through the recursive calls:
- Deepest recursive calls create leaf nodes
- As recursion unwinds, parent nodes are created and connected to their children
- Finally, the root node with all descendants is returned
The time complexity is O(n)
where n
is the length of the string, as we process each character once. The space complexity is O(h)
for the recursion stack, where h
is the height of the resulting tree.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with a simple example: "1(2)(3(4))"
This represents a tree where:
- Root = 1
- Left child = 2 (leaf)
- Right child = 3 with left child = 4
Step 1: Initial call with "1(2)(3(4))"
- Find first
(
at position 1 - Extract root value:
s[:1]
= "1", createroot = TreeNode(1)
- Start counting from position 1
Step 2: Find left subtree
start = 1
,cnt = 0
- Position 1:
(
→cnt = 1
- Position 2:
2
→ skip - Position 3:
)
→cnt = 0
✓ Found complete subtree! - Left subtree string:
s[2:3]
= "2" - Recursively call
dfs("2")
- No parentheses found, return
TreeNode(2)
- No parentheses found, return
- Set
root.left = TreeNode(2)
- Update
start = 4
Step 3: Find right subtree
- Continue from position 4,
cnt = 0
- Position 4:
(
→cnt = 1
- Position 5:
3
→ skip - Position 6:
(
→cnt = 2
- Position 7:
4
→ skip - Position 8:
)
→cnt = 1
- Position 9:
)
→cnt = 0
✓ Found complete subtree! - Right subtree string:
s[5:9]
= "3(4)" - Recursively call
dfs("3(4)")
Step 4: Parse right subtree "3(4)"
- Find first
(
at position 1 - Extract root value: "3", create
node = TreeNode(3)
- Count parentheses starting at position 1:
- Position 1:
(
→cnt = 1
- Position 2:
4
→ skip - Position 3:
)
→cnt = 0
✓
- Position 1:
- Left subtree string:
s[2:3]
= "4" - Recursively call
dfs("4")
- No parentheses, return
TreeNode(4)
- No parentheses, return
- Set
node.left = TreeNode(4)
- No more content, so no right child
- Return
TreeNode(3)
with left child 4
Step 5: Complete the tree
- Set
root.right = TreeNode(3)
(which has left child 4) - Return the complete tree
Final tree structure:
1 / \ 2 3 / 4
The parenthesis counting ensures we correctly identify (2)
as the complete left subtree and (3(4))
as the complete right subtree, even though the right subtree contains nested parentheses.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8class Solution:
9 def str2tree(self, s: str) -> TreeNode:
10 """
11 Constructs a binary tree from a string representation.
12 Format: "val(left_subtree)(right_subtree)"
13 Example: "4(2(3)(1))(6(5))" represents a tree with root 4,
14 left subtree rooted at 2, and right subtree rooted at 6.
15 """
16
17 def build_tree(tree_string: str) -> TreeNode:
18 # Base case: empty string returns None
19 if not tree_string:
20 return None
21
22 # Find the first opening parenthesis
23 first_paren_index = tree_string.find('(')
24
25 # If no parenthesis found, this is a leaf node
26 if first_paren_index == -1:
27 return TreeNode(int(tree_string))
28
29 # Extract the root value (everything before first parenthesis)
30 root_value = int(tree_string[:first_paren_index])
31 root = TreeNode(root_value)
32
33 # Track the start position for parsing subtrees
34 start_position = first_paren_index
35 parenthesis_count = 0
36
37 # Iterate through the string to find matching parentheses
38 for current_index in range(first_paren_index, len(tree_string)):
39 current_char = tree_string[current_index]
40
41 # Increment counter for opening parenthesis
42 if current_char == '(':
43 parenthesis_count += 1
44 # Decrement counter for closing parenthesis
45 elif current_char == ')':
46 parenthesis_count -= 1
47
48 # When we find a complete balanced section (count reaches 0)
49 if parenthesis_count == 0:
50 if start_position == first_paren_index:
51 # This is the left subtree
52 # Extract content between parentheses and recursively build
53 root.left = build_tree(tree_string[start_position + 1 : current_index])
54 start_position = current_index + 1
55 else:
56 # This is the right subtree
57 # Extract content between parentheses and recursively build
58 root.right = build_tree(tree_string[start_position + 1 : current_index])
59
60 return root
61
62 # Start the recursive tree building process
63 return build_tree(s)
64
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 /**
18 * Constructs a binary tree from a string representation.
19 * Format: "val(left_subtree)(right_subtree)"
20 * Example: "4(2(3)(1))(6(5))" represents a tree with root 4
21 *
22 * @param s the string representation of the binary tree
23 * @return the root of the constructed binary tree
24 */
25 public TreeNode str2tree(String s) {
26 return buildTree(s);
27 }
28
29 /**
30 * Recursively builds the tree from string using DFS approach.
31 *
32 * @param s the current substring to process
33 * @return the root node of the subtree constructed from the string
34 */
35 private TreeNode buildTree(String s) {
36 // Base case: empty string returns null
37 if (s.isEmpty()) {
38 return null;
39 }
40
41 // Find the first opening parenthesis
42 int firstParenthesisIndex = s.indexOf("(");
43
44 // If no parenthesis found, this is a leaf node
45 if (firstParenthesisIndex == -1) {
46 return new TreeNode(Integer.parseInt(s));
47 }
48
49 // Extract the root value (everything before first parenthesis)
50 int rootValue = Integer.parseInt(s.substring(0, firstParenthesisIndex));
51 TreeNode root = new TreeNode(rootValue);
52
53 // Track the starting position for parsing children
54 int startPosition = firstParenthesisIndex;
55 // Counter to track matching parentheses
56 int parenthesisCounter = 0;
57
58 // Iterate through the string to find matching parentheses
59 for (int i = firstParenthesisIndex; i < s.length(); i++) {
60 char currentChar = s.charAt(i);
61
62 // Increment counter for opening parenthesis
63 if (currentChar == '(') {
64 parenthesisCounter++;
65 }
66 // Decrement counter for closing parenthesis
67 else if (currentChar == ')') {
68 parenthesisCounter--;
69 }
70
71 // When counter reaches 0, we found a complete subtree expression
72 if (parenthesisCounter == 0) {
73 if (startPosition == firstParenthesisIndex) {
74 // First complete expression is the left subtree
75 // Extract content between parentheses (startPosition + 1 to i)
76 root.left = buildTree(s.substring(startPosition + 1, i));
77 // Update start position for potential right subtree
78 startPosition = i + 1;
79 } else {
80 // Second complete expression is the right subtree
81 // Extract content between parentheses (startPosition + 1 to i)
82 root.right = buildTree(s.substring(startPosition + 1, i));
83 }
84 }
85 }
86
87 return root;
88 }
89}
90
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Constructs a binary tree from a string representation
16 * Format: "val(left_subtree)(right_subtree)"
17 * @param s - String representation of the tree
18 * @return Root node of the constructed tree
19 */
20 TreeNode* str2tree(string s) {
21 return buildTree(s);
22 }
23
24private:
25 /**
26 * Recursively builds the tree from string representation
27 * @param s - Current substring to process
28 * @return Root node of the current subtree
29 */
30 TreeNode* buildTree(const string& s) {
31 // Base case: empty string returns null
32 if (s.empty()) {
33 return nullptr;
34 }
35
36 // Find the first opening parenthesis
37 size_t firstParenPos = s.find('(');
38
39 // If no parenthesis found, this is a leaf node
40 if (firstParenPos == string::npos) {
41 return new TreeNode(stoi(s));
42 }
43
44 // Extract the root value (everything before first parenthesis)
45 int rootValue = stoi(s.substr(0, firstParenPos));
46 TreeNode* root = new TreeNode(rootValue);
47
48 // Track parentheses to find matching pairs
49 int startPos = firstParenPos;
50 int parenthesesCount = 0;
51
52 // Iterate through the string to find left and right subtrees
53 for (size_t i = firstParenPos; i < s.size(); ++i) {
54 if (s[i] == '(') {
55 ++parenthesesCount;
56 } else if (s[i] == ')') {
57 --parenthesesCount;
58 }
59
60 // When parentheses are balanced, we've found a complete subtree
61 if (parenthesesCount == 0) {
62 if (startPos == firstParenPos) {
63 // First balanced group is the left subtree
64 // Extract content between parentheses
65 root->left = buildTree(s.substr(startPos + 1, i - startPos - 1));
66 startPos = i + 1; // Move start position for next subtree
67 } else {
68 // Second balanced group is the right subtree
69 root->right = buildTree(s.substr(startPos + 1, i - startPos - 1));
70 }
71 }
72 }
73
74 return root;
75 }
76};
77
1/**
2 * Definition for a binary tree node.
3 */
4class TreeNode {
5 val: number;
6 left: TreeNode | null;
7 right: TreeNode | null;
8
9 constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
10 this.val = (val === undefined ? 0 : val);
11 this.left = (left === undefined ? null : left);
12 this.right = (right === undefined ? null : right);
13 }
14}
15
16/**
17 * Constructs a binary tree from a string representation
18 * Format: "val(left_subtree)(right_subtree)"
19 * @param s - String representation of the tree
20 * @returns Root node of the constructed tree
21 */
22function str2tree(s: string): TreeNode | null {
23 return buildTree(s);
24}
25
26/**
27 * Recursively builds the tree from string representation
28 * @param s - Current substring to process
29 * @returns Root node of the current subtree
30 */
31function buildTree(s: string): TreeNode | null {
32 // Base case: empty string returns null
33 if (s.length === 0) {
34 return null;
35 }
36
37 // Find the first opening parenthesis
38 const firstParenPos = s.indexOf('(');
39
40 // If no parenthesis found, this is a leaf node
41 if (firstParenPos === -1) {
42 return new TreeNode(parseInt(s));
43 }
44
45 // Extract the root value (everything before first parenthesis)
46 const rootValue = parseInt(s.substring(0, firstParenPos));
47 const root = new TreeNode(rootValue);
48
49 // Track parentheses to find matching pairs
50 let startPos = firstParenPos;
51 let parenthesesCount = 0;
52
53 // Iterate through the string to find left and right subtrees
54 for (let i = firstParenPos; i < s.length; i++) {
55 if (s[i] === '(') {
56 parenthesesCount++;
57 } else if (s[i] === ')') {
58 parenthesesCount--;
59 }
60
61 // When parentheses are balanced, we've found a complete subtree
62 if (parenthesesCount === 0) {
63 if (startPos === firstParenPos) {
64 // First balanced group is the left subtree
65 // Extract content between parentheses
66 root.left = buildTree(s.substring(startPos + 1, i));
67 startPos = i + 1; // Move start position for next subtree
68 } else {
69 // Second balanced group is the right subtree
70 root.right = buildTree(s.substring(startPos + 1, i));
71 }
72 }
73 }
74
75 return root;
76}
77
Time and Space Complexity
Time Complexity: O(n²)
where n
is the length of the input string.
The analysis breaks down as follows:
- The function traverses each character in the string to find matching parentheses, which takes
O(n)
time in the worst case - String slicing operations
s[start + 1 : i]
create new substrings, which takesO(k)
time wherek
is the length of the substring - In the worst case, we process every character of the string through recursive calls, and each recursive call involves creating substrings
- The total work done across all recursive calls involves processing each character and potentially copying it during substring operations, leading to
O(n²)
complexity
Space Complexity: O(n)
where n
is the length of the input string.
The space complexity consists of:
- Recursion stack: The maximum depth of recursion equals the height of the tree, which in the worst case (skewed tree) is
O(n)
- Substring creation: Each recursive call creates new substrings from the original string. While individual substrings are created, the total space used for all substrings across all recursive calls is bounded by
O(n)
since we're essentially partitioning the original string - Tree nodes: Creating
O(n)
tree nodes, but this is typically not counted as auxiliary space since it's the output - Overall auxiliary space complexity is
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Parsing of Negative Numbers
The current implementation assumes all node values are non-negative integers. When the input contains negative numbers like "-4(2)(3)"
, the code will fail because it doesn't handle the minus sign properly when finding the first parenthesis or extracting the root value.
Why it fails: The find('(')
method locates the first parenthesis, but when extracting s[:first_paren_index]
, if there's a negative number, int()
conversion works fine. However, the real issue arises in more complex parsing scenarios where negative numbers can be confused with operators or delimiters.
Solution:
def str2tree(self, s: str) -> TreeNode:
def build_tree(tree_string: str) -> TreeNode:
if not tree_string:
return None
# Find where the number ends (could be negative)
i = 0
# Handle negative sign
if i < len(tree_string) and tree_string[i] == '-':
i += 1
# Parse all digits
while i < len(tree_string) and tree_string[i].isdigit():
i += 1
# If we've consumed the entire string, it's a leaf node
if i == len(tree_string):
return TreeNode(int(tree_string))
# Extract root value and create node
root = TreeNode(int(tree_string[:i]))
# Now i points to the first '('
start_position = i
parenthesis_count = 0
# Continue with parenthesis matching logic...
for current_index in range(i, len(tree_string)):
if tree_string[current_index] == '(':
parenthesis_count += 1
elif tree_string[current_index] == ')':
parenthesis_count -= 1
if parenthesis_count == 0:
if start_position == i:
root.left = build_tree(tree_string[start_position + 1 : current_index])
start_position = current_index + 1
else:
root.right = build_tree(tree_string[start_position + 1 : current_index])
return root
return build_tree(s)
2. Edge Case: Single Child Nodes
Another pitfall is not properly handling cases where a node has only a right child but no left child. According to the problem description, the first pair of parentheses always represents the left child. If a node has only a right child, it should be represented as "val()(right_child)"
with empty parentheses for the missing left child.
Example that might cause issues: "1()(2)"
- node 1 with no left child and right child 2.
The current code handles this correctly because when it encounters "()"
, the recursive call with an empty string returns None
, which is the desired behavior. However, developers might overlook testing this case or might try to "optimize" by skipping empty parentheses, which would break the logic.
3. Stack Overflow with Deep Trees
For extremely deep trees, the recursive approach could cause a stack overflow due to Python's recursion limit (default is around 1000).
Solution for deep trees: Use an iterative approach with an explicit stack, or increase the recursion limit:
import sys sys.setrecursionlimit(10000) # Increase if needed
However, be cautious with this approach as it could lead to actual stack overflow at the system level for very large inputs.
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Want a Structured Path to Master System Design Too? Don’t Miss This!