DSA sheet · Trees · Construction / serialization pattern

Flatten Binary Tree to Linked List

This is the first problem where we change the shape of the tree itself instead of only reading it. That is why the sheet puts it in the "construction" pattern. The teacher first spots that the answer is just the preorder order of the nodes, and solves it the easy way with a list (brute force). Then she removes the list and the recursion completely, and solves it with O(1) extra space using a Morris-style walk. That second idea is new and very popular in interviews, so we go through it slowly.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

What is a linked list?

A linked list is a chain of boxes. Each box holds a value and a pointer to the next box. The last box points to nothing (None). Here we don't get a separate linked-list class. We must build the chain out of the tree nodes, using the right pointer as "next".

given by LeetCode, don't write this in the solution
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left     # must become None everywhere
        self.right = right   # will act as the "next" pointer

Preorder traversal (recap)

Preorder means: visit the root first, then the whole left subtree, then the whole right subtree. (A subtree is a node together with everything below it.) The same rule is used again inside every subtree.

plain preorder, just to remember the shape
def preorder(node):
    if node is None:
        return
    print(node.val)          # root
    preorder(node.left)      # then left
    preorder(node.right)     # then right

Words used on this page

Part A · Brute force: store the preorder, then relink

LeetCode 114

1The question in simple words

You get the root of a binary tree. Turn it into a "linked list" that hangs only to the right:

input
        1
       / \
      2   5
     / \   \
    3   4   6
after flattening
1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

2What the constraints tell us

3Intuition: the answer is the preorder

Read the flattened chain from top to bottom: 1, 2, 3, 4, 5, 6. Now do a preorder of the input tree:

Preorder = 1 2 3 4 5 6, exactly the chain. So the first idea is: collect the nodes in preorder, then tie each node to the next one.

4Building the logic

Rule: rearrange, don't rebuild

The teacher stresses this. It is tempting to make new nodes 1 → 2 → 3 … and return them. That is not allowed. The tree's structure stays the same (each node still has a value, a left and a right). We are only allowed to re-point the existing nodes.

Doubt 1: so what do I store in the list: the values or the nodes?
→ The nodes themselves. With only values (1, 2, 3…) you would have to create new nodes, which means a new tree. With the node objects, you can reach each real node later and change its pointers.

Rule: for every position i, set right to the next node and left to None

Say the list is nodes = [1, 2, 3, 4, 5, 6] (the real node objects). Look at index i:

Watch it happen:

Rule: stop at the second-last node

The loop uses i + 1. At the last index there is no i + 1, so we'd go out of range. We also don't need to touch the last node at all.

Doubt 2: but don't I need to set the last node's left and right to None?
→ No. The last node in a preorder is always a leaf. Preorder writes a node before its children, so if the last node had any child, that child would come after it, and it wouldn't be last. A leaf already has left = None and right = None.
Doubt 3: why not change the pointers while doing the preorder itself?
→ If you set node.right = node.left before visiting the old right subtree, that subtree is lost (nothing points to it any more). Storing first and relinking afterwards is safe, because the whole order is saved before we touch any pointer.

5Approach steps

  1. Make an empty list nodes.
  2. Do a preorder traversal and append each node (not its value).
  3. For i from 0 to the second-last index: nodes[i].left = None, nodes[i].right = nodes[i+1].
  4. Return nothing (the tree has been changed in place).

6Code (Python)

Flatten, brute force with a list
class Solution:
    def flatten(self, root):
        nodes = []
        self.preorder(root, nodes)           # 1. save nodes in preorder
        for i in range(len(nodes) - 1):      # 2. up to the second-last
            nodes[i].left = None
            nodes[i].right = nodes[i + 1]

    def preorder(self, node, nodes):
        if node is None:
            return
        nodes.append(node)                   # root
        self.preorder(node.left, nodes)      # left
        self.preorder(node.right, nodes)     # right

7Code line by line

linewhat it means
nodes = []The extra list that holds every node. This is the O(n) extra space we will remove later.
self.preorder(root, nodes)Fill the list in root, left, right order.
if node is None: returnBase case of the traversal. It also handles the empty tree: the list stays empty.
nodes.append(node)Store the node object itself, so we can rewire it later.
for i in range(len(nodes) - 1):i goes 0 … n−2. The last node is a leaf and needs nothing. For an empty or one-node tree this range is empty, so nothing happens.
nodes[i].left = NoneClear the left side (needed for nodes like 1 and 2 that had left children).
nodes[i].right = nodes[i + 1]"Next" pointer: tie this node to the one after it in preorder.

8Dry run

Tree from step 1. First the preorder fills the list:

nodes123456indexes 0 … 5
  1. i = 0: 1.left = None (2 is removed from the left), 1.right = 2. The old right part (5 → 6) is no longer reachable from 1, but it is safe, because it's stored in the list.
  2. i = 1: 2.left = None (3 leaves the left), 2.right = 3.
  3. i = 2: 3.left = None, 3.right = 4.
  4. i = 3: 4.left = None, 4.right = 5. Now 5 → 6 is attached again.
  5. i = 4: 5.left = None, 5.right = 6.
  6. The loop stops (i = 5 would need index 6). 6 is a leaf, already fine. The chain is 1 → 2 → 3 → 4 → 5 → 6 ✓
call stack while the preorder is at node 3
pre(1)pre(2)pre(3)
at node 6
pre(1)pre(5)pre(6)

The stack is only as tall as the path from the root, which is why the recursion costs O(height).

9Complexity & remember

It works and every student should be able to write it. But it is not optimised, because of the extra list and the call stack. An interviewer will ask: "can you do it without extra space?"

Remember brute forcePreorder → list of nodes → for i up to n−2: left = None, right = nodes[i+1].

Part B · Optimal: Morris-style flattening, O(1) space

1The question (same as Part A)

Same input, same output. The new goal: no list, no recursion, only a few pointer variables.

2What the constraints tell us

3Intuition: lift the left side over to the right

The teacher calls this the Morris traversal idea. If you've never seen it, that's normal. You learn it once and then it feels easy. (She jokes that only geniuses invent it on the spot.)

Stand at a node. In the final answer, everything must be on the right. So:

  1. Pick up the node's whole left subtree and put it on the right.
  2. The old right subtree must not be lost. Hang it under the rightmost node of the subtree you just moved.
  3. Step down to the right and repeat.

Why the rightmost node? In preorder, the old right subtree comes right after the last node of the left subtree. The last node of a subtree in preorder is found by going right, right, right… as far as possible. So that's where the right subtree belongs.

Doubt: the teacher also mentions another way: start from the bottom-right leaf and connect upwards. What is that?
→ She only mentions it and moves on. The idea: visit nodes in reverse preorder (right, then left, then root), keep a prev pointer to the node handled just before, and set node.right = prev, node.left = None. It's correct, but it uses recursion, so it costs O(height) stack space. The Morris walk is better on space. (Short code for it is in the revision part.)

4Building the logic step by step (her derivation)

Step 1: save the right side before overwriting it

At node 1, we want 1.right = 1.left. But if we write that line first, the old right subtree (5 → 6) is gone, because nothing points to it any more. So first copy it into a temporary variable:

Order matterstemp = curr.right  →  curr.right = curr.left  →  curr.left = None

After these 3 lines the tree looks like this, with 5 → 6 waiting in temp:

tree
1
 \
  2
 / \
3   4
temp
5
 \
  6
Doubt 1: why set curr.left = None?
→ After copying, 2 is linked on both sides of 1. The answer needs every left to be None, so we cut the left link.

Step 2: find where to re-attach temp

Should 5 → 6 go under 3 or under 4? Preorder of the moved part is 2, 3, 4, so 5 must come after 4. And 4 is the rightmost node of the subtree starting at 2 (2 → right → 4, and 4 has no right). So we write a small helper:

helper: go right until you can't
def rightMost(self, node):
    while node.right is not None:   # stop at the node with NO right child
        node = node.right
    return node

We call it with curr.right (that's 2, the top of the moved subtree). It checks: does 2 have a right? Yes → move to 4. Does 4 have a right? No → return 4.

Doubt 2: why does the loop check node.right and not node?
→ If we looped until node itself is None, we would walk off the end and return None, and then None.right = temp would crash. We want to stop on the last real node, so we check its right child.

Step 3: attach temp to the rightmost node's RIGHT

rm.right = temp. Then 4 → 5 → 6. We only connect 5; 6 comes along because it is already linked to 5. Now temp has done its job and is free to be reused at the next node.

Doubt 3: could I attach temp to 4's left instead?
→ No. The final shape uses only right pointers. Putting 5 on a left would create a new left link that we'd have to fix again later.

Step 4: move down and repeat, so wrap it in a loop

After one round, only the part at node 1 is correct. The next node to fix is curr.right (node 2). So curr = curr.right, inside while curr:, until we fall off the end.

At node 2: temp = 4 → 5 → 6. 2.right = 3, 2.left = None. rightMost(3) = 3 (3 has no right). 3.right = temp. Now the chain is 1 → 2 → 3 → 4 → 5 → 6. It looks finished, but the code can't know that until curr has visited every node.

Step 5: skip all that work when there is no left child

At node 3 the teacher notices: 3's left is already None. Saving temp, moving None to the right and searching the rightmost node makes no sense. So all four lines go inside if curr.left:.

Doubt 4: then why is curr = curr.right outside the if?
→ Whether or not the node had a left child, we must still move on to the next node. If that line were inside the if, a node with no left child would never move forward, and the loop would run forever.

5Approach steps

  1. curr = root.
  2. While curr is not None:
  3. If curr.left exists: save temp = curr.right, move the left subtree to the right, set left to None, find the rightmost node of the moved subtree, hang temp on its right.
  4. In all cases, curr = curr.right.
  5. Return nothing.

6Code (Python)

Flatten, optimal O(1) space
class Solution:
    def flatten(self, root):
        curr = root
        while curr:
            if curr.left:                        # only if there is a left part to move
                temp = curr.right                # 1. save the right side
                curr.right = curr.left           # 2. move left subtree to the right
                curr.left = None                 # 3. clear the left
                rm = self.rightMost(curr.right)  # 4. end of the moved part
                rm.right = temp                  # 5. re-attach the old right side
            curr = curr.right                    # always step forward

    def rightMost(self, node):
        while node.right:
            node = node.right
        return node

7Code line by line

linewhat it means
curr = rootA walking pointer (the teacher calls it an alias of root). We don't move root itself.
while curr:Visit nodes down the right chain until we fall off. This loop replaces recursion, so there's no call stack. An empty tree skips the loop.
if curr.left:Only nodes with a left subtree need rewiring.
temp = curr.rightKeep the old right subtree safe before overwriting the pointer.
curr.right = curr.leftThe left subtree now hangs on the right.
curr.left = NoneCut the duplicate left link.
rm = self.rightMost(curr.right)Walk right from the top of the moved subtree to its last preorder node.
rm.right = tempThe old right subtree continues after that node. temp is now free.
curr = curr.rightGo to the next node in the chain, whether or not we rewired.
while node.right: node = node.rightStop on the node whose right is None and return it.

8Dry run

Same tree as Part A: 1 (2 (3, 4), 5 (–, 6)).

  1. curr = 1, left = 2 exists. temp = 5→6. 1.right = 2, 1.left = None. rightMost(2): 2 has right 4 → move; 4 has no right → return 4. 4.right = 5→6.
  2. curr = 2 (1.right). left = 3 exists. temp = 4→5→6. 2.right = 3, 2.left = None. rightMost(3): 3 has no right → return 3. 3.right = 4→5→6.
  3. curr = 3: left is None → skip the if. curr = 4.
  4. curr = 4: no left → curr = 5.
  5. curr = 5: no left → curr = 6.
  6. curr = 6: no left → curr = None. The loop ends. Chain: 1 → 2 → 3 → 4 → 5 → 6 ✓
after step 1
1
 \
  2
 / \
3   4
     \
      5
       \
        6
after step 2
1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

9Complexity & remember

Time: it looks like O(n log n), but it is O(2n)

The outer loop visits every node once → O(n). Inside, rightMost walks down a right edge, which looks like up to log n more steps per node, so you might guess O(n log n). The teacher says that is too pessimistic. Count how many times each node is touched:

nodetouched by rightMosttouched by currtotal
1–once1
2once (round at 1)once2
4once (round at 1)once2
3once (round at 2)once2
5, 6–once1

So in the worst case each node is touched at most twice: once by curr, and once while searching for a rightmost node. That gives O(2n) = O(n).

Remember Morris flattenAt each node with a left child: save right → move left to right → clear left → hang saved right on the rightmost node. Then curr = curr.right (always).

Part C · Revision page

Brute force (list)Optimal (Morris-style)
main ideastore preorder nodes, then link i → i+1move left subtree to the right, re-attach old right at the rightmost node
moves through tree withrecursiona while loop on curr
extra storagelist of n nodes + call stack3 pointers
empty treelist stays empty, loop doesn't runwhile curr doesn't run
timeO(2n)O(2n) (each node touched ≤ 2 times)
spaceO(n)O(1)
the other idea she mentions: reverse preorder with prev (O(height) stack)
class Solution:
    def flatten(self, root):
        self.prev = None
        self.build(root)

    def build(self, node):
        if node is None:
            return
        self.build(node.right)      # right first...
        self.build(node.left)       # ...then left
        node.right = self.prev      # the node handled just before is "next"
        node.left = None
        self.prev = node
If you remember only 5 lines 1. The flattened order is the preorder order.
2. Rearrange the same nodes. Never create new ones.
3. Brute force: list of nodes, then left = None, right = next.
4. Optimal: save right in temp, move left to right, clear left, hang temp on the rightmost node.
5. curr = curr.right sits outside the if. Time O(n), space O(1).
Mistakes to avoid ✗ writing curr.right = curr.left before saving curr.right (the right subtree is lost)
✗ forgetting curr.left = None
✗ attaching temp to the rightmost node's left
✗ rightMost looping until node is None (returns None, then crashes)
✗ putting curr = curr.right inside the if (infinite loop)
✗ storing values instead of nodes in the brute force
test it yourself (paste under any of the solutions above)
root = TreeNode(1, TreeNode(2, TreeNode(3), TreeNode(4)),
                   TreeNode(5, None, TreeNode(6)))
Solution().flatten(root)

out, node = [], root
while node:
    assert node.left is None     # every left must be empty
    out.append(node.val)
    node = node.right
print(out)                       # [1, 2, 3, 4, 5, 6]

Based on this video: Flatten Binary Tree to Linked List