606. Construct String From Binary Tree - Explanation
Description
Represent the binary tree as a string by writing each node's value before its children. Enclose each child's representation in parentheses, with the left child before the right child.
A leaf contributes only its value. If a node has only a left child, omit the missing right child entirely. If it has only a right child, write () for the missing left child before the right child's parentheses. These rules preserve the tree's structure while removing unnecessary empty parentheses.
Examples use level-order arrays. After the root, each present node contributes left and right child entries in queue order. null marks a missing child, and trailing missing entries may be omitted.
Example 1:
Input: root = [7,2,9,null,4]
Output: "7(2()(4))(9)"Explanation: The left subtree is 2()(4): its missing left child needs an empty pair before its right child. Enclosing both root subtrees produces 7(2()(4))(9).
Example 2:
Input: root = [-3,null,6]
Output: "-3()(6)"Explanation: The root has a right child but no left child. The empty pair in -3()(6) preserves that distinction.
Example 3:
Input: root = [5,0]
Output: "5(0)"Explanation: The root has only a left child, so 5(0) is sufficient. No empty pair is needed for the missing right child, and the leaf 0 has no child parentheses.
Constraints:
- The tree contains between 1 and 10000 nodes.
-1000 <= Node.val <= 1000
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Tree Traversals - Understanding preorder traversal (root-left-right) for string construction
- Recursion / DFS - Traversing trees recursively to build the string representation
- String Building - Efficiently concatenating strings using StringBuilder or list accumulation
- Stack-based Iteration - Converting recursive DFS to iterative using an explicit stack
1. Depth First Search
Intuition
We need to create a string representation using preorder traversal with parentheses. The key observation is handling empty subtrees: we must include empty parentheses () for a missing left child only when a right child exists (to preserve the tree structure), but we can omit parentheses for a missing right child entirely.
Algorithm
- If the node is
null, return an empty string. - Recursively get string representations of left and right subtrees.
- If both children exist, return
"val(left)(right)". - If only the right child exists, return
"val()(right)"(empty parentheses for missing left). - If only the left child exists, return
"val(left)"(no parentheses needed for missing right). - If the node is a leaf, return just its value as a string.
# 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 tree2str(self, root: Optional[TreeNode]) -> str:
if not root:
return ""
cur = root.val
left = self.tree2str(root.left)
right = self.tree2str(root.right)
if left and right:
return f"{cur}({left})({right})"
if right:
return f"{cur}()({right})"
if left:
return f"{cur}({left})"
return str(cur)Time & Space Complexity
- Time complexity:
- Space complexity:
2. Depth First Search (Optimal)
Intuition
The previous approach creates many intermediate strings during concatenation, which is inefficient. Instead, we can use a StringBuilder (or list) to accumulate characters. We always add an opening parenthesis before processing a node and a closing parenthesis after, then trim the outermost parentheses at the end.
Algorithm
- Create a result list or StringBuilder.
- Define a preorder function that:
- Returns immediately if the node is
null. - Appends
"("followed by the node's value. - If
leftisnullbutrightexists, appends"()". - Recursively processes
left, thenright. - Appends
")".
- Returns immediately if the node is
- Call preorder on the root.
- Join the result and remove the first and last characters (the extra outer parentheses).
# 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 tree2str(self, root: Optional[TreeNode]) -> str:
res = []
def preorder(root):
if not root:
return
res.append("(")
res.append(str(root.val))
if not root.left and root.right:
res.append("()")
preorder(root.left)
preorder(root.right)
res.append(")")
preorder(root)
return "".join(res)[1:-1]Time & Space Complexity
- Time complexity:
- Space complexity:
3. Iterative DFS
Intuition
We can convert the recursive approach to iterative using an explicit stack. The challenge is knowing when we have finished processing a node's subtrees so we can add the closing parenthesis. We track the last visited node to determine whether we are returning from the right subtree.
Algorithm
- Use a stack for traversal and track the last visited node.
- While current node exists or stack is not empty:
- If current exists, append
"(val", handle missing left child if right exists, push to stack, move to left child. - Otherwise, peek at the stack top:
- If right child exists and was not last visited, move to right child.
- Otherwise, pop the node, append
")", mark it as last visited.
- If current exists, append
- Remove the outer parentheses from the result and return.
# 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 tree2str(self, root: Optional[TreeNode]) -> str:
if not root:
return ""
res = []
stack = []
last_visited = None
cur = root
while cur or stack:
if cur:
res.append(f"({cur.val}")
if not cur.left and cur.right:
res.append("()")
stack.append(cur)
cur = cur.left
else:
top = stack[-1]
if top.right and last_visited != top.right:
cur = top.right
else:
stack.pop()
res.append(")")
last_visited = top
return "".join(res)[1:-1]Time & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Omitting Empty Parentheses for Missing Left Child
When only the right child exists, you must include empty parentheses () for the missing left child to preserve tree structure. Without this, the right child would be incorrectly interpreted as the left child during reconstruction.
# Wrong: Missing empty parentheses
if right:
return f"{cur}({right})" # Output: "1(3)" instead of "1()(3)"
# Correct: Include empty parentheses for missing left
if right:
return f"{cur}()({right})"Adding Unnecessary Parentheses for Missing Right Child
When only the left child exists, you should NOT add empty parentheses for the missing right child. The problem specifically states that empty parentheses for missing right children should be omitted since they don't affect tree reconstruction.
Sign in to join the discussion