Start learning free

LESSON 307 OF 414 · LINKED LISTS

Flatten Multilevel List in Scala: Iterative with a getTail Helper

Practice O(n) Read the full explanation

You are given a doubly linked list which in addition to the next and previous pointers, it could have a child pointer, which may or may not point to a separate doubly linked list. These child lists may have one or more children of their own, and so on, to produce a multilevel data structure, as shown in the example below.

Flatten the list so that all the nodes appear in a single-level, doubly linked list. You are given the head of the first level of the list.

 

Example 1:

Input: head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
Output: [1,2,3,7,8,11,12,9,10,4,5,6]
Explanation:

The multilevel linked list in the input is as follows:



After flattening the multilevel linked list it becomes:


Example 2:

Input: head = [1,2,null,3]
Output: [1,3,2]
Explanation:

The input multilevel linked list is as follows:

  1---2---NULL
  |
  3---NULL

Example 3:

Input: head = []
Output: []

 

How multilevel linked list is represented in test case:

We use the multilevel linked list from Example 1 above:

 1---2---3---4---5---6--NULL
         |
         7---8---9---10--NULL
             |
             11--12--NULL

The serialization of each level is as follows:

[1,2,3,4,5,6,null]
[7,8,9,10,null]
[11,12,null]

To serialize all levels together we will add nulls in each level to signify no node connects to the upper node of the previous level. The serialization becomes:

[1,2,3,4,5,6,null]
[null,null,7,8,9,10,null]
[null,11,12,null]

Merging the serialization of each level and removing trailing nulls we obtain:

[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]

 


Note:

Your algorithm should be iterative (non-recursive) and run in O(n) time.


STEP 1 OF 12
    Ctrl + Enter to check

    SCALA · LINKED LISTS

    Flatten Multilevel List in Scala, explained

    Problem overview

    The method flatten receives the head of a doubly linked list. Each Node has prev, next and child links, and a child may point to the head of another doubly linked list, which can have children of its own. The method must rewire the nodes in place so that everything sits on a single level, and return the head.

    In the first example the top level is 1-2-3-4-5-6. Node 3 has the child list 7-8-9-10, and node 8 has the child list 11-12. The flattened order is [1, 2, 3, 7, 8, 11, 12, 9, 10, 4, 5, 6]: each child list goes right after its parent. With 1-2 and a child 3 under node 1, the result is [1, 3, 2]. An empty list stays empty.

    Approach: splice as you walk, with no recursion

    Walk the list with a single cursor. When the cursor is on a node with a child, splice the child list in between that node and its old successor. The cursor then moves to the next node, which is now the first node of the spliced child list. That is the key to avoiding recursion: if a node in the child list has a child of its own, the cursor reaches that node later and splices its child in the same way. Deeper levels are flattened when the walk gets to their parents, so nested levels disappear without any extra bookkeeping.

    The splice needs the tail of the child list, the last node on the child's own level, because that is the node that must point to the saved successor. A small helper, getTail, finds it by following next until there is none. It only walks the child's own level, since deeper levels have not been spliced yet. Each node is walked by getTail at most once, so the whole job stays linear.

    The lesson also has a recursive version, where the helper returns the tail of an already flattened child. This version keeps the work in one loop.

    Step by step

    1. Create a walker. var node = head moves along the list. head stays unchanged because it is the return value.
    2. Loop while node is not null, so every node is visited.
    3. Check for a child. Nodes without one just let the loop move on.
    4. Save the successor in val next = node.next. Once node.next is overwritten with the child, the rest of the level would otherwise be unreachable.
    5. Find the child list's tail with val childTail = getTail(node.child).
    6. Link the node to its child in both directions: node.next = node.child and node.child.prev = node. Both lines read node.child, so they come before it is cleared.
    7. Clear the child: node.child = null.
    8. Reconnect the rest with childTail.next = next, and set next.prev = childTail only when next is not null.
    9. Advance outside the if: node = node.next. After a splice this is the first child node, so the loop walks through the spliced nodes and flattens nested levels.
    10. Return head after the loop. Returning node would be wrong, since it is null by then.
    11. Write getTail above flatten. It takes a Node and returns one.
    12. Walk to the last node. Copy the parameter into var n, advance it while n.next != null, and end with n. The test is on n.next, because the goal is the last node, not the null after it.

    The solution in Scala

    class Solution {
    
        def getTail(node: Node): Node = {
            var n = node
            while (n.next != null) {
                n = n.next
            }
            n
        }
    
        def flatten(head: Node): Node = {
            var node = head
            while (node != null) {
                if (node.child != null) {
                    val next = node.next
                    val childTail = getTail(node.child)
                    node.next = node.child
                    node.child.prev = node
                    node.child = null
                    childTail.next = next
                    if (next != null) {
                        next.prev = childTail
                    }
                }
                node = node.next
            }
            head
        }
    }
    

    The Scala details worth knowing:

    • Method order. getTail is defined before flatten to match the lesson's steps, but inside a class the order does not matter to the compiler.
    • Parameters are immutable. getTail can't move its node parameter, so it copies it into var n.
    • The last expression is the value. n and head are the final lines of their methods and need no return.
    • val inside a loop. next and childTail are declared with val inside the if body. Each pass through the body creates new ones, so they are not shared between iterations.
    • null checks are explicit. getTail assumes its argument is not null, which is safe because it is only called after node.child != null. Calling it with null would throw a NullPointerException.

    Complexity

    Time: O(n), where n is the number of nodes. The main loop visits each node once. getTail walks the child list's own level before it is spliced, and a node is part of exactly one such walk, so those walks add at most another n steps.

    Space: O(1). There is no recursion and no extra collection, only a few references. The list is changed in place.

    Edge cases and common mistakes

    • Empty list. The loop doesn't run and null is returned.
    • A child on the last node. next is null, so the child list just becomes the end, and the null check prevents a crash.
    • Stopping getTail one step too late. A loop on n != null would end with null, and childTail.next = next would then fail.
    • Moving the cursor inside the if. Then nodes without a child would never advance the cursor, and the loop would not finish.
    • Skipping over the spliced nodes. Jumping to next after a splice would skip the child nodes and leave their own children unflattened.
    • Leaving child pointers. Without node.child = null, the list keeps stale children, and walking it again would splice the same lists in a second time.
    • Setting only next. Both directions need updating, otherwise walking backward through prev breaks.

    Testing

    • The first example gives [1, 2, 3, 7, 8, 11, 12, 9, 10, 4, 5, 6].
    • 1-2 with a child 3 under node 1 gives [1, 3, 2].
    • An empty list gives an empty list.
    • 1 with a child 2 whose own child is 3-4 gives [1, 2, 3, 4].
    • Walking the flattened first example backward through prev from the last node gives [6, 5, 4, 10, 9, 12, 11, 8, 7, 3, 2, 1].
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free