Facebook Pixel

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:

  1. Finding the root value (the number before any parentheses)
  2. Using parenthesis counting to identify where the left subtree ends
  3. Recursively parsing the left subtree content
  4. If more content exists after the left subtree, recursively parsing the right subtree
  5. 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:

  1. We need to recursively parse nested parentheses structures in the string
  2. 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
  3. The recursive nature of the problem (a tree contains smaller trees) naturally fits the DFS pattern
  4. 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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Extract the root value (the number before any parentheses)
  2. Find where the left subtree ends (when parentheses balance)
  3. 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 and start = 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 time cnt 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
  • When start != p (second time cnt reaches 0), we've found the right subtree
    • Extract and parse similarly: root.right = dfs(s[start + 1 : i])

String Slicing: The solution uses Python's string slicing to extract substrings:

  • s[:p] - gets the root value
  • s[start + 1 : i] - gets content between parentheses (excluding the parentheses themselves)

Tree Construction: The tree is built bottom-up through the recursive calls:

  1. Deepest recursive calls create leaf nodes
  2. As recursion unwinds, parent nodes are created and connected to their children
  3. 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 Evaluator

Example 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", create root = 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)
  • 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 ✓
  • Left subtree string: s[2:3] = "4"
  • Recursively call dfs("4")
    • No parentheses, return TreeNode(4)
  • 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 takes O(k) time where k 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More