Start learning free

LESSON 348 OF 438 · BINARY TREES

Recover a Tree From Preorder Traversal in JavaScript: Stack of Depths

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

    JAVASCRIPT · BINARY TREES

    Recover Tree From Preorder in JavaScript, explained

    Problem overview

    A binary tree is written in preorder: 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, so it has no dashes. 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 input is always a valid traversal.

    For "1-2--3--4-5--6--7" the nodes are 1 (depth 0), 2 (1), 3 (2), 4 (2), 5 (1), 6 (2) and 7 (2). Node 2 is the first child of the root, so it's the left child. 3 and 4 follow it one level deeper, so they are its children. 5 is back at depth 1, a second child of the root, so it's the right child, and 6 and 7 belong to it. The tree is [1,2,5,3,4,6,7] in level order.

    Approach: keep the path from the root

    Preorder order has a useful property: after a node comes its subtree, and the next node written at a lower depth belongs to some node on the path from the root to the last node. So the string can be read once, from left to right, while keeping a stack that holds that path. The node at position d of the stack has depth d.

    When a new node with depth depth is read, its parent is the node at depth depth - 1. Nodes deeper than that are finished, because their subtrees ended when the depth dropped. So pop the stack until it holds exactly depth nodes. The top is then the parent.

    The new node is attached as the parent's left child if that is still empty, and as the right child otherwise. This works because preorder writes the left subtree first, so a parent's first child is always its left one, which also matches the rule for a node with a single child. Finally the new node is pushed, since it is now the deepest node of the path.

    On "1-2--3---4-5--6---7", after reading 4 the stack is 1, 2, 3, 4. Then 5 arrives with depth 1, so the stack is cut down to just 1. The root has a left child already, so 5 becomes its right child. The depth went back up by two levels at once, and the stack handles that without special cases.

    Step by step

    1. Create stack as an empty array. It will hold the path from the root down to the last node read.
    2. Create i, starting at 0. It is the position being read in traversal. Every loop that reads a character advances it, so the string is read once.
    3. Write the main while (i < traversal.length) loop. Each pass reads one node: its dashes, then its value.
    4. Create depth, starting at 0, to count the dashes of this node.
    5. Loop while traversal[i] === '-'. No bounds check is needed because the string never ends with a dash: a value always follows.
    6. In that loop, add one to depth and advance i. When it stops, i is on the first digit.
    7. Create value, starting at 0, to build the number digit by digit.
    8. Loop while i is inside the string and traversal[i] is not a dash. A value ends at the next node's dashes or at the end of the string.
    9. Update the value: multiply by 10 and add Number(traversal[i]), then advance i. Reading 4 and then 0 gives 4, then 4 * 10 + 0 = 40.
    10. Create node as a new TreeNode(value).
    11. Drop the finished nodes: while stack.length > depth, pop. Afterwards the stack holds the path down to depth depth - 1, and its top is the parent.
    12. Pop the top inside that loop. The popped nodes stay attached to their own parents; they just can't get new children.
    13. Check stack.length > 0. Only the root, the first node read, finds the stack empty, because it is the only node at depth 0.
    14. Name the top parent, the element at index stack.length - 1. It stays on the stack because it may get a second child later.
    15. If parent.left is null, set parent.left to node. A first child is a left child.
    16. Otherwise set parent.right to node. A parent has at most two children, and the second goes on the right.
    17. Push node onto the stack. The stack again holds the path from the root to the newest node.
    18. After the loop, return stack[0]. The root is the first node read and is never popped, because every other node has depth 1 or more and so leaves at least the root on the stack.

    The solution in JavaScript

    class Solution {
        recoverFromPreorder(traversal) {
            let stack = [];
            let i = 0;
            while (i < traversal.length) {
                let depth = 0;
                while (traversal[i] === '-') {
                    depth++;
                    i++;
                }
                let value = 0;
                while (i < traversal.length && traversal[i] !== '-') {
                    value = value * 10 + Number(traversal[i]);
                    i++;
                }
                let node = new TreeNode(value);
                while (stack.length > depth) {
                    stack.pop();
                }
                if (stack.length > 0) {
                    let parent = stack[stack.length - 1];
                    if (parent.left === null) {
                        parent.left = node;
                    } else {
                        parent.right = node;
                    }
                }
                stack.push(node);
            }
            return stack[0];
        }
    }

    Some details about JavaScript:

    • Strings can be indexed. traversal[i] is a one-character string. Reading past the end gives undefined, which is not equal to '-'. That is why the dash loop needs no bounds check, while the digit loop checks i < traversal.length to be explicit.
    • Number('4') converts one digit. The digit loop only sees characters that aren't dashes, which in a valid input are digits. Number('-') would give NaN, which is why the dashes are consumed before the digits.
    • Values fit in a double exactly. The largest value, 1,000,000,000, is far below 2 to the power 53, so building it with value * 10 + digit loses nothing.
    • parent.left === null works because TreeNode sets both children to null. A missing child is null, not undefined.

    Complexity

    Every character of the string is read once. Each node is pushed once and popped at most once, so the stack work is O(n) overall, where n is the number of nodes. The total time is O(L), where L is the length of the string. The stack holds one path, so the extra space is O(h) for a tree of height h, on top of the tree itself.

    Edge cases and common mistakes

    • Multi-digit values. Reading one character as a whole value would split 12 into 1 and 2. In "5-12--7---40--9" the values 12 and 40 have two digits each.
    • A node with one child. The child goes on the left. For "5-12--7---40--9", the root 5 has only 12, as its left child, and the tree is [5,12,null,7,9,40] in level order.
    • The depth jumps back several levels. The while loop that pops handles any number of levels, where a single pop() would only handle one.
    • A single-node string. "7" has depth 0 and an empty stack, so the node is pushed and returned as the root with no children.
    • Popping the parent. Pop only while stack.length > depth. Popping one more would remove the parent, and the second child would be attached to the wrong node.

    Testing

    The method returns these trees, shown in level order with trailing nulls left out:

    • "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].
    Keep going with JavaScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free