Start learning free

LESSON 308 OF 415 · LINKED LISTS

Flatten a Multilevel Doubly Linked List in Swift: Iterative Splicing

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

    SWIFT · LINKED LISTS

    Flatten Multilevel List in Swift, explained

    Problem overview

    The method flatten receives the head of a doubly linked list. Besides prev and next, each node has a child link, which may point to the head of another doubly linked list, and that list's nodes may have children too. The method must turn the structure into one level, in place, and return the head.

    In the 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. Each child list goes right after its parent and before the parent's old successor, so the result is 1, 2, 3, 7, 8, 11, 12, 9, 10, 4, 5, 6. Afterwards no node has a child, and the prev links agree with the next links.

    Approach: splice as the walk reaches each parent

    The other lesson on this problem flattens each child list recursively. This one needs no recursion. It uses one walker that moves through the list with next. When the walker lands on a node with a child, it splices the child list in without flattening it first.

    The splice has the same shape every time. Save the node's successor, find the last node of the child list, join the node to the child, and join the child's last node to the saved successor. A helper getTail finds that last node by following next from the child until there is none.

    The child list may still contain nodes with children of their own, and that's fine. After the splice, the walker moves to node.next, which is now the first child node. It then walks through the spliced nodes, and when it reaches one that has a child, it splices that one in the same way. Deeper levels get flattened when the walk arrives at their parents.

    This also explains why getTail only needs to follow next on the child's own level. Any node on that level with a child will get its child spliced later, after the walker has moved on.

    Step by step

    1. Start a walker. var node = head. The parameter head stays unchanged because it is what the method returns.
    2. Loop while node != nil.
    3. Check for a child with node!.child != nil. The ! is safe because the loop condition just checked the node.
    4. Save the successor in let next = node!.next, before the link is overwritten.
    5. Find the child list's last node with getTail(node!.child) and keep it in childTail.
    6. Link the node and the child both ways: node!.next = node!.child and node!.child!.prev = node.
    7. Clear node!.child. It still has to be read by the two lines before, so it can only be cleared after them.
    8. Link the child's tail to the saved successor: childTail!.next = next, and when next exists, next!.prev = childTail.
    9. Advance with node = node!.next, outside the if. After a splice this enters the child list, so the walk reaches deeper levels.
    10. Return head. Returning node would be wrong, because it is nil after the loop.
    11. Write getTail. It copies its parameter into a mutable var node and advances while node!.next != nil. It stops on the last node, not on the nil after it.

    The solution in Swift

    class Solution {
    
        func getTail(_ node: Node?) -> Node? {
            var node = node
            while node!.next != nil {
                node = node!.next
            }
            return node
        }
    
        func flatten(_ head: Node?) -> Node? {
            var node = head
            while node != nil {
                if node!.child != nil {
                    let next = node!.next
                    let childTail = getTail(node!.child)
                    node!.next = node!.child
                    node!.child!.prev = node
                    node!.child = nil
                    childTail!.next = next
                    if next != nil {
                        next!.prev = childTail
                    }
                }
                node = node!.next
            }
            return head
        }
    }
    

    Swift details to notice:

    • Parameters are constants. var node = node in getTail makes a new mutable variable that shadows the parameter, so the loop can move it.
    • A crash on nil. getTail unwraps its argument with !. Calling it with nil stops the program at run time. It is only called on a child that was checked to exist.
    • Classes are shared. Node is a class, so node!.next = ... changes the real node. That is why the list is flattened in place and returning head is enough.
    • Optional comparison. next != nil works for any optional type. It doesn't require Node to be Equatable.

    Complexity

    Time: O(n). Each node is walked by the main loop once. It is also walked once by getTail, as part of the level it belongs to, and only before its parent is spliced. Each node therefore costs a constant number of steps in total.

    Space: O(1). There is no recursion and nothing is allocated, only a few references. This is the advantage over the recursive version, which uses one stack frame per level of nesting, and which can revisit nodes once per level above them.

    Edge cases and common mistakes

    • Empty list. flatten(nil) returns nil, because the loop never runs.
    • No children. The list comes back unchanged.
    • Child on the last node of a level. next is nil, so the child's tail ends the list, and no back link is set. 1, 2 with a child 3, 4 on node 2 becomes 1, 2, 3, 4.
    • Child on the head. 1, 2 with a child 3 on node 1 becomes 1, 3, 2.
    • Children inside children. The example needs this: node 8 sits inside the list that was spliced in for node 3, and its child is spliced when the walker gets to 8.
    • Stopping getTail too late. The loop condition must test node!.next. Testing node itself would run past the last node and return nil.
    • Not clearing child. The result would still carry child links, so it would not be a single-level list.

    Testing

    • The example above gives 1, 2, 3, 7, 8, 11, 12, 9, 10, 4, 5, 6, with no children left and matching prev links.
    • An empty list gives an empty list.
    • 1, 2, 3 with no children stays 1, 2, 3.
    • 1, 2 with a child 3, 4 on node 2 gives 1, 2, 3, 4.
    • 1, 2 with a child 3 on node 1 gives 1, 3, 2.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free