Start learning free

LESSON 351 OF 441 · BINARY TREES

Recover a Tree From Preorder in C#: One Pass with a Path 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

    C# · BINARY TREES

    Recover Tree From Preorder in C#, explained

    Problem overview

    A binary tree is written as a string in preorder: a node, then its whole left subtree, then its whole right subtree. Before each value there are as many dashes as the node's depth, with the root at depth 0 and no dashes. A node with a single child always has it on the left. The method RecoverFromPreorder receives that string, traversal, and returns the root of the rebuilt TreeNode tree.

    In "1-2--3--4-5--6--7", the node 1 is the root. The values 2 and 5 have one dash, so they are its children, with 2 on the left because it comes first. The two nodes after 2 have two dashes, so they are the children of 2, and the two after 5 are the children of 5. Read level by level, the tree is [1, 2, 5, 3, 4, 6, 7].

    Values can have several digits, as in "5-12--7---40--9". Here 5 has only the child 12, on its left, 12 has the children 7 and 9, and 7 has one child, 40. Read level by level, the tree is [5, 12, null, 7, 9, 40].

    Approach: keep the path from the root to the newest node

    Preorder always writes a node right after its parent's earlier nodes, so the parent of a new node is on the path from the root to the node just written. A stack can hold exactly that path: the node at position d of the stack has depth d.

    For every node read, with depth d and a value:

    1. Drop nodes from the top of the stack until it holds exactly d nodes. What is left is the path down to depth d - 1, and its top is the new node's parent.
    2. If the parent has no left child yet, attach the new node there. Otherwise attach it on the right. Preorder writes the left subtree first, so the first child is the left one, and a parent has at most two children.
    3. Push the new node. It may be the parent of the next one.

    The nodes that were dropped are finished: their subtrees are complete, and they stay attached to their own parents. The stack is only a bookmark for finding parents.

    Here is "1-2--3---4-5--6---7", where the depth falls from 3 to 1 at once:

    DepthValueStack after the node is pushedWhere it is attached
    011root
    121, 2left of 1
    231, 2, 3left of 2
    341, 2, 3, 4left of 3
    151, 5right of 1
    261, 5, 6left of 5
    371, 5, 6, 7left of 6

    When 5 arrives at depth 1, the stack 1, 2, 3, 4 is cut down to its first node, 1. Node 1 already has the left child 2, so 5 goes to the right.

    Step by step

    1. Create the stack as an empty List<TreeNode> named stack. Add pushes at the end, and the top is the last element.
    2. Create i, the position in traversal, starting at 0. The string is read once, left to right.
    3. Loop while i < traversal.Length. Each pass reads one node.
    4. Start depth at 0 for this node.
    5. Count the dashes with a loop that runs while traversal[i] == '-'.
    6. Inside it, increase depth and move i forward. 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 with a loop that runs while i is inside the string and the character is not a dash.
    9. Append each digit: value = value * 10 + (traversal[i] - '0'), then move i. Reading 4 then 0 gives 4, then 40.
    10. Create the node with new TreeNode(value).
    11. Cut the stack with a loop that runs while stack.Count > depth.
    12. Remove the top with RemoveAt(stack.Count - 1).
    13. Check that the stack is not empty. Only the root finds it empty, since it is the only node at depth 0.
    14. Name the parent: the last element, stack[stack.Count - 1]. It stays on the stack because it may get a second child.
    15. Attach on the left if parent.left is still null.
    16. Otherwise attach on the right.
    17. Push the node with Add.
    18. Return stack[0]. The root is never removed, because every other node has depth 1 or more and so never cuts the stack below one node.

    The solution in C#

    class Solution {
        public TreeNode RecoverFromPreorder(string traversal) {
            List<TreeNode> stack = new List<TreeNode>();
            int i = 0;
            while (i < traversal.Length) {
                int depth = 0;
                while (traversal[i] == '-') {
                    depth++;
                    i++;
                }
                int value = 0;
                while (i < traversal.Length && traversal[i] != '-') {
                    value = value * 10 + (traversal[i] - '0');
                    i++;
                }
                TreeNode node = new TreeNode(value);
                while (stack.Count > depth) {
                    stack.RemoveAt(stack.Count - 1);
                }
                if (stack.Count > 0) {
                    TreeNode parent = stack[stack.Count - 1];
                    if (parent.left == null) {
                        parent.left = node;
                    } else {
                        parent.right = node;
                    }
                }
                stack.Add(node);
            }
            return stack[0];
        }
    }
    

    The C# details worth knowing:

    • Indexing a string gives a char. traversal[i] is a character, so it is compared with a character literal in single quotes, '-'. A double-quoted "-" would be a string, and the comparison would not compile.
    • Digit characters to numbers. Subtracting two chars gives an int in C#, so '7' - '0' is the number 7. The digit characters have consecutive codes, which is what makes this work.
    • The order of the two conditions matters. In i < traversal.Length && traversal[i] != '-', the bounds check comes first, so traversal[i] is never read past the end. Reading outside a string throws an IndexOutOfRangeException. The dashes loop has no bounds check, because a valid traversal never ends with a dash.
    • A List as a stack. RemoveAt on the last index removes the top without shifting anything. A Stack<T> would do for popping, but the code also needs stack.Count to equal the depth and stack[0] to reach the root, and the list does both.
    • Nodes are references. Setting parent.left = node links the object itself. Later removing it from the List doesn't affect the tree.
    • Values fit in an int. The largest value is 1,000,000,000, below the int maximum of 2,147,483,647, and building it digit by digit never goes past it.

    Complexity

    Let n be the length of traversal.

    Time: O(n). Each character is read once. Each node is pushed once and removed at most once, so all the stack operations together are proportional to the number of nodes, which is at most n.

    Space: O(h) besides the tree. The stack holds one path, so its size is the height of the tree, at most the number of nodes. The tree itself takes O(n) memory, as the answer.

    Edge cases and common mistakes

    • A single node. "1" reads depth 0, finds the stack empty, pushes the node and returns it: [1].
    • A single child goes left. "1-2" gives [1, 2], with 2 on the left of 1. The attach rule does this on its own, because the first child of any parent goes left.
    • A chain. "1-2--3---4----5" gives [1, 2, null, 3, null, 4, null, 5]: every node is the left child of the previous one.
    • A big jump up. In "1-2--3---4-5" the depth falls from 3 to 1, and the cutting loop removes three nodes from the stack in a single pass.
    • Two children. "1-2-3" gives [1, 2, 3]. Node 2 is cut off when 3 arrives at depth 1, and 3 becomes the right child because 1 already has a left child.
    • Multi-digit values. Reading one character per node would split 12 into 1 and 2. The digits loop keeps reading until the next dash.
    • Counting dashes as digits. Treating a dash as part of a number breaks the depth count. The dashes loop must end on the first digit.
    • Cutting the stack to depth - 1. The loop leaves depth nodes on the stack, so that the top is the parent at depth depth - 1. Keeping one more would attach a node to its own sibling.
    • Popping the parent after attaching a child. A parent may still need a second child, so it stays until a shallower node cuts it off.

    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].
    • "1" gives [1].
    • "1-2-3" gives [1, 2, 3].
    • "1-401--349---90--88" gives [1, 401, null, 349, 88, 90].
    Keep going with C#About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free