Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

536. Construct Binary Tree from String - Leetcode Solution

Problem Description

The Leetcode problem "Construct Binary Tree from String" asks you to construct a binary tree from a string consisting of integers and parentheses. The string represents a preorder traversal of a binary tree, where:

  • Each integer represents a node's value.
  • Parentheses (...) enclose the left and right subtrees.

For example, given the string "4(2(3)(1))(6(5))", you should build the following binary tree:
        4
       / \
      2   6
     / \  /
    3  1 5
    

Constraints:
  • The input string is non-empty and contains only digits, parentheses, and possibly a leading negative sign for negative numbers.
  • There is exactly one valid binary tree for each input string.
  • All node values are unique and may be negative or positive integers.

Thought Process

At first glance, the problem resembles parsing a mathematical expression. Each number is a node, and each pair of parentheses indicates a subtree. A brute-force approach might involve recursively splitting the string at every parenthesis, but this quickly becomes messy due to nested structures.

To optimize, we can recognize that the string is a preorder traversal, and the parentheses always wrap the left and right children, in order. This suggests a recursive or stack-based parsing approach, where we process numbers and recursively parse their children as we encounter parentheses.

The main challenge is handling nested parentheses and multi-digit (or negative) numbers. We need to carefully keep track of our current position in the string and be able to parse out each subtree correctly.

Solution Approach

We can solve the problem using a recursive parsing strategy. Here’s how:

  1. Recursive Function: Define a helper function that takes the string and a current index, and returns the constructed subtree and the next index after that subtree.
  2. Parsing Numbers: At each call, parse the node value (which may be negative and may have multiple digits).
  3. Parsing Children: After the node value, check if the next character is a '('. If so, recursively parse the left subtree. After the left subtree, check again for a right subtree (which will also be in parentheses).
  4. Handling Parentheses: Each recursive call returns when it sees a closing ')', so we always know when a subtree ends.
  5. Building the Tree: At each step, create a new tree node with the parsed value, and attach the left and right children as returned by the recursive calls.

This approach is efficient because it processes each character in the string exactly once, and never splits or copies substrings unnecessarily.

Example Walkthrough

Let's walk through the input "4(2(3)(1))(6(5))":

  1. Start at index 0, parse 4 as the root node.
  2. Next character is '(', so parse left child:
    • Parse 2 as left child of 4.
    • Next '(', parse left child:
      • Parse 3 as left child of 2. No more children, return node 3.
    • Next '(', parse right child:
      • Parse 1 as right child of 2. No more children, return node 1.
    • Return node 2 with children 3 and 1.
  3. Next '(', parse right child:
    • Parse 6 as right child of 4.
    • Next '(', parse left child:
      • Parse 5 as left child of 6. No more children, return node 5.
    • No more children, return node 6 with left child 5.
  4. Return root node 4 with left child 2 (with its children 3 and 1) and right child 6 (with child 5).

Time and Space Complexity

Brute-force Approach: If we tried to split substrings at every parenthesis, each split operation would take O(n) time, leading to an overall complexity worse than O(n2).

Optimized Recursive Parsing: Our approach visits each character in the string exactly once, so the time complexity is O(n), where n is the length of the input string.
Space complexity is also O(n), due to the recursion stack and the space needed to store the binary tree nodes.

Summary

To solve "Construct Binary Tree from String", we leverage the preorder traversal structure and use a recursive parser that walks through the string character by character. This avoids inefficient substring operations and handles nested expressions cleanly. The result is an elegant, linear-time solution that builds the binary tree as we parse, with each node and subtree constructed at exactly the right moment.

Code Implementation

# 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 str2tree(self, s: str) -> TreeNode:
        if not s:
            return None

        def helper(index):
            if index >= len(s):
                return None, index

            # Parse the integer value (may be negative and multi-digit)
            sign = 1
            if s[index] == '-':
                sign = -1
                index += 1
            num = 0
            while index < len(s) and s[index].isdigit():
                num = num * 10 + int(s[index])
                index += 1
            node = TreeNode(sign * num)

            # Parse left child if '('
            if index < len(s) and s[index] == '(':
                index += 1  # skip '('
                node.left, index = helper(index)
                index += 1  # skip ')'

            # Parse right child if '('
            if index < len(s) and s[index] == '(':
                index += 1  # skip '('
                node.right, index = helper(index)
                index += 1  # skip ')'

            return node, index

        root, _ = helper(0)
        return root
      
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* str2tree(string s) {
        int idx = 0;
        return helper(s, idx);
    }
    TreeNode* helper(const string& s, int& idx) {
        if (idx >= s.size()) return nullptr;

        // Parse value (may be negative)
        int sign = 1;
        if (s[idx] == '-') {
            sign = -1;
            idx++;
        }
        int num = 0;
        while (idx < s.size() && isdigit(s[idx])) {
            num = num * 10 + (s[idx] - '0');
            idx++;
        }
        TreeNode* node = new TreeNode(sign * num);

        // Parse left child
        if (idx < s.size() && s[idx] == '(') {
            idx++; // skip '('
            node->left = helper(s, idx);
            idx++; // skip ')'
        }

        // Parse right child
        if (idx < s.size() && s[idx] == '(') {
            idx++; // skip '('
            node->right = helper(s, idx);
            idx++; // skip ')'
        }
        return node;
    }
};
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode str2tree(String s) {
        if (s == null || s.length() == 0) return null;
        int[] idx = new int[1];
        return helper(s, idx);
    }

    private TreeNode helper(String s, int[] idx) {
        if (idx[0] >= s.length()) return null;

        // Parse value (may be negative)
        int sign = 1;
        if (s.charAt(idx[0]) == '-') {
            sign = -1;
            idx[0]++;
        }
        int num = 0;
        while (idx[0] < s.length() && Character.isDigit(s.charAt(idx[0]))) {
            num = num * 10 + (s.charAt(idx[0]) - '0');
            idx[0]++;
        }
        TreeNode node = new TreeNode(sign * num);

        // Parse left child
        if (idx[0] < s.length() && s.charAt(idx[0]) == '(') {
            idx[0]++; // skip '('
            node.left = helper(s, idx);
            idx[0]++; // skip ')'
        }

        // Parse right child
        if (idx[0] < s.length() && s.charAt(idx[0]) == '(') {
            idx[0]++; // skip '('
            node.right = helper(s, idx);
            idx[0]++; // skip ')'
        }
        return node;
    }
}
      
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

/**
 * @param {string} s
 * @return {TreeNode}
 */
var str2tree = function(s) {
    if (!s || s.length === 0) return null;
    let i = 0;
    function helper() {
        if (i >= s.length) return null;

        // Parse value (may be negative)
        let sign = 1;
        if (s[i] === '-') {
            sign = -1;
            i++;
        }
        let num = 0;
        while (i < s.length && s[i] >= '0' && s[i] <= '9') {
            num = num * 10 + (s[i].charCodeAt(0) - '0'.charCodeAt(0));
            i++;
        }
        let node = new TreeNode(sign * num);

        // Parse left child
        if (i < s.length && s[i] === '(') {
            i++; // skip '('
            node.left = helper();
            i++; // skip ')'
        }

        // Parse right child
        if (i < s.length && s[i] === '(') {
            i++; // skip '('
            node.right = helper();
            i++; // skip ')'
        }
        return node;
    }
    return helper();
};