Start learning free

LESSON 348 OF 438 · BINARY TREES

Recover a Tree From Preorder Traversal in Python: One Pass With a Stack

Practice O(n) Read the full explanation

We walk a binary tree in preorder: first a node, then its whole left subtree, then its whole right subtree. For every node we write D dashes, where D is the node's depth, followed right away by the node's value. The root has depth 0, so it has no dashes; its children have depth 1, their children depth 2, and so on.

If a node has only one child, that child is its left child.

Given the string traversal written this way, rebuild the tree and return its root.

The tests print the tree you return level by level, from left to right, with null where a child is missing and the trailing nulls left out. For example, [1,2,null,3] is the tree with root 1, 2 as its left child and 3 as the left child of 2.

 

Example 1:

Input: traversal = "1-2--3--4-5--6--7"
Output: [1,2,5,3,4,6,7]
       1
     /   \
    2     5
   / \   / \
  3   4 6   7

Explanation: "-2" and "-5" have one dash: they are the root's children, 2 first, so 2 is the left one. "--3" and "--4" come right after "-2", so they are the children of 2; "--6" and "--7" come after "-5", so they are the children of 5.

Example 2:

Input: traversal = "1-2--3---4-5--6---7"
Output: [1,2,5,3,null,6,null,4,null,7]
        1
       / \
      2   5
     /   /
    3   6
   /   /
  4   7

Explanation: After "---4" (depth 3) comes "-5" (depth 1): the depth goes back up by two levels at once, so 5 is the second child of the root, its right child. Every node with a single child has it on the left.

Example 3:

Input: traversal = "5-12--7---40--9"
Output: [5,12,null,7,9,40]
      5
     /
    12
   /  \
  7    9
 /
40

Explanation: Values can have several digits: the dashes before a node end where its digits start. The root 5 has a single child, 12, so it is a left child.

 

Constraints:

  • The number of nodes in the tree is in the range [1, 1000].
  • Every node's value is between 1 and 109.
  • traversal is a valid preorder walk of a tree, written as described above.
STEP 1 OF 18
    Ctrl + Enter to check

    PYTHON · BINARY TREES

    Recover Tree From Preorder in Python, explained

    Problem overview

    A binary tree has been written as a string in preorder: first a node, then its whole left subtree, then its whole right subtree. Each node is written as D dashes followed by its value, where D is the node's depth. The root has depth 0 and no dashes, its children have one dash, and so on. If a node has only one child, that child is the left one.

    The method recoverFromPreorder receives the string traversal and returns the root of the rebuilt tree. Values are between 1 and 1,000,000,000, so they can have several digits, and the string is always a valid walk.

    For "1-2--3--4-5--6--7": the root is 1. The nodes with one dash, 2 and 5, are its children, and 2 comes first, so it is the left child. The nodes --3 and --4 follow 2 and are its children. The nodes --6 and --7 follow 5. The tree is [1, 2, 5, 3, 4, 6, 7] in level order.

    For "1-2--3---4-5--6---7", after ---4 at depth 3 comes -5 at depth 1. The depth drops by two at once, so 5 is the second child of the root. The result is [1, 2, 5, 3, null, 6, null, 4, null, 7].

    Approach: keep the path from the root

    Preorder writes a node right after its parent's earlier subtrees, so a node's parent is always the closest earlier node that is one level shallower. All we need to know while reading is the current path from the root down to the last node created.

    That path is a stack. Position d of the stack holds the node at depth d. When a new node arrives with depth d:

    • The nodes at positions d and deeper are finished: preorder has left their subtrees behind. Remove them.
    • The top of the stack is now the node at depth d - 1, the parent. Attach the new node to it.
    • Push the new node, which now ends the path.

    Attaching is simple because of the order. A parent's first child is its left child, as preorder writes the left subtree first, and a single child is a left child by the statement's rule. If the left slot is already taken, the node is the second child, so it goes to the right.

    On "5-12--7---40--9": 5 is the root. -12 has depth 1, so its parent is 5, and it becomes the left child. --7 goes under 12, ---40 under 7. Then --9 has depth 2: the stack, which was [5, 12, 7, 40], is cut back to [5, 12], and 9 becomes the right child of 12, because 12's left slot is taken by 7.

    Step by step

    1. Create stack, an empty list for the path from the root.
    2. Create i at 0. It is the position being read, and it only moves forward, so the string is read once.
    3. Loop while i < len(traversal). Each pass reads one node: its dashes, then its digits.
    4. Start depth at 0 for the current node.
    5. Count the dashes with a loop that runs while traversal[i] is '-'. It needs no bounds check, because a value always follows the dashes.
    6. In that loop, add 1 to depth and move i on. When it stops, i is on the first digit.
    7. Start value at 0. The number is built digit by digit.
    8. Read the digits while i is inside the string and the character isn't a dash. Here the bounds check is needed, because the last number ends the string.
    9. Append each digit: multiply value by 10, add int(traversal[i]) and move i. Reading 4 and then 0 gives 4, then 40.
    10. Create node as a TreeNode holding value.
    11. Cut the stack back with a loop that runs while len(stack) > depth.
    12. Pop in that loop. The removed nodes stay attached to their own parents.
    13. Check if stack. Only the root finds the stack empty.
    14. Name the top parent, which is stack[-1]. It stays on the stack because it may get a second child.
    15. If parent.left is None, set parent.left to node.
    16. Otherwise, set parent.right to node.
    17. Push node. It is the deepest node of the path now.
    18. Return stack[0]. The root is never popped, because every other node has depth 1 or more.

    The solution in Python

    class Solution:
        def recoverFromPreorder(self, traversal: str) -> TreeNode:
            stack = []
            i = 0
            while i < len(traversal):
                depth = 0
                while traversal[i] == '-':
                    depth += 1
                    i += 1
                value = 0
                while i < len(traversal) and traversal[i] != '-':
                    value = value * 10 + int(traversal[i])
                    i += 1
                node = TreeNode(value)
                while len(stack) > depth:
                    stack.pop()
                if stack:
                    parent = stack[-1]
                    if parent.left is None:
                        parent.left = node
                    else:
                        parent.right = node
                stack.append(node)
            return stack[0]
    

    The Python details worth a closer look:

    • Strings are read by index. traversal[i] is a one-character string, so it is compared with '-' and turned into a number with int(...). Calling int on a single digit character is safe here, because only digits can appear outside dashes.
    • Short-circuit order. In i < len(traversal) and traversal[i] != '-', the bounds check comes first. Reversed, the last digit would be followed by an IndexError on the next test. In the dash loop the order doesn't matter, because a dash is always followed by a digit.
    • is None. Compare with None using is, which tests identity. A missing child is the value None.
    • The stack is a plain list. stack[-1] reads the last element, pop() removes it and append adds one. stack[0] is the first element, the root.
    • Only the stack changes shape. Popping a node doesn't remove it from the tree, since its parent's left or right still refers to it. Python keeps the node alive as long as something refers to it.

    Complexity

    Time: O(n), where n is the length of the string. The index i only moves forward, so each character is read once. Every node is pushed once and popped at most once, so the stack work is bounded by the number of nodes, which is at most the length of the string.

    Space: O(h) for the stack, where h is the height of the tree, plus the tree itself. The stack holds one root-to-node path. A chain of 1000 nodes gives a stack of 1000 entries.

    Edge cases and common mistakes

    • A single node. "7" reads depth 0 and value 7, finds an empty stack and pushes the root. The result is a tree with just 7.
    • Multi-digit values. In "5-12--7---40--9", the digit loop reads 12 and 40 whole. Treating each character as a value would produce wrong nodes.
    • A single child is a left child. "5-12" puts 12 on the left of 5, never on the right.
    • The depth can drop by several levels. That is why the stack is cut with a loop instead of one pop().
    • Dashes and the root. For "1-2-3", both 2 and 3 have depth 1. After 2 is read, the stack is cut back to [1], so 3 becomes the right child of 1.
    • Popping to the wrong size. Cutting to depth + 1 or depth - 1 attaches the node to the wrong parent. The stack must have exactly depth entries before the node is attached.
    • Returning the last node. The last node pushed is a leaf. The root is stack[0], because it never gets popped.

    Testing

    • "1-2--3--4-5--6--7" gives [1, 2, 5, 3, 4, 6, 7].
    • "1-2--3---4-5--6---7" gives [1, 2, 5, 3, null, 6, null, 4, null, 7].
    • "5-12--7---40--9" gives [5, 12, null, 7, 9, 40].
    • "7" gives [7].
    • "1-2-3" gives [1, 2, 3].
    Keep going with PythonAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free