Start learning free

LESSON 307 OF 415 · LINKED LISTS

Flatten a Multilevel Doubly Linked List in Swift: Recursion

Practice O(n) Recursive 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 recursive and run in O(n · d) time, where d is the maximum nesting depth of the child lists.


STEP 1 OF 13
    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 whose nodes have prev and next links and one more link, child. A child points to the head of a separate doubly linked list, and that list's nodes may have children of their own, so the structure has several levels. The task is to turn it into a single-level list, in place, and return the head.

    In the example, the top level is 1, 2, 3, 4, 5, 6. Node 3 has a child list 7, 8, 9, 10, and node 8 has a child list 11, 12. Each child list is spliced in right after its parent, before the parent's old successor. The result reads 1, 2, 3, 7, 8, 11, 12, 9, 10, 4, 5, 6. After flattening, every child is nil and every prev link matches the next links.

    The statement asks for a recursive algorithm that runs in O(n) time.

    Approach: flatten the sub-list first, then splice it in

    Walk along one level. Most nodes have no child, and the walk moves on. When a node has a child, it needs three things:

    1. The rest of the level after the node must not get lost, so save node.next.
    2. The child list must be flat before it is spliced in. That is the same problem on a smaller list, so the code calls itself on the child.
    3. The flat child list goes between the node and the saved successor. The node's next becomes the child, and the last node of the child list gets the saved successor as its next.

    To connect the end of the child list, the code needs to know which node is last. So the recursive helper returns the tail of the level it flattened. The caller keeps it as childTail and uses it for the reconnection.

    After the splice, the walk continues with node.next, which is now the first child node. The walk therefore goes through the spliced nodes too, and finishes on the true last node of the level, which becomes the tail to return.

    Step by step

    1. Delegate to a helper. flatten calls flattenLevel(head) and returns head. Flattening only rewires links, so the first node is still first.
    2. Declare flattenLevel above flatten. It takes a Node? and returns a Node?. The attribute @discardableResult tells the compiler that ignoring the returned tail is fine, which flatten does.
    3. Make a mutable copy of the parameter. var node = node shadows the constant parameter. Another variable, tail, starts equal to it and tracks the last node seen. For an empty level the loop never runs and the helper returns nil.
    4. Loop while node != nil. Every node of this level is visited.
    5. Test for a child with node!.child != nil. Only these nodes need rewiring.
    6. Save the successor in next.
    7. Flatten the child recursively and keep its tail in childTail.
    8. Link the node and its child in both directions: node!.next = node!.child and node!.child!.prev = node.
    9. Clear node!.child, so the flat list has no child links. This must come after the two lines above, which still read it.
    10. Link the end of the child list to the saved successor: childTail!.next = next. If next exists, also set next!.prev = childTail. If the node was the last on its level, there is nothing to point back.
    11. Record and advance. After the if, set tail = node and node = node!.next.
    12. Return tail after the loop.

    The solution in Swift

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

    Swift details to notice:

    • @discardableResult. Without it, Swift warns that the result of flattenLevel(head) is unused. The attribute is a promise that ignoring the value is normal. The recursive call, which does use the value, is unaffected.
    • Shadowing a parameter. Function parameters are constants. var node = node creates a new local variable with the same name, initialised from the parameter. It is the usual way to get a mutable copy.
    • Nodes are class instances. node!.next = ... changes the actual node that other variables refer to, which is what in-place flattening needs.
    • Every ! has a reason. node! is safe inside the loop because the condition checked it. childTail! is safe because a child list has at least one node, so its tail exists.

    Complexity

    Time. Every node is spliced at most once and handled by the loop of its own level. In addition, the walk of a level continues over the nodes that were just spliced in, so a node is looked at again by each level above it. For a shallow structure this is close to O(n). For deep nesting the extra visits add up: a chain of 100 single-node levels makes 5,050 visits for 100 nodes. The exact bound is O(n * depth).

    Space: O(depth). There is one stack frame per level of nesting, and nothing else is allocated. A very deep structure could exhaust the call stack.

    Edge cases and common mistakes

    • Empty list. flatten(nil) returns nil, because the loop never runs.
    • No children. The list is returned unchanged.
    • A child on the last node of a level. next is nil, so childTail!.next becomes nil and the back link is skipped. The list 1, 2 with a child 3, 4 on node 2 becomes 1, 2, 3, 4.
    • A child on the head. The head stays the head: 1, 2 with a child 3 on node 1 becomes 1, 3, 2.
    • Forgetting to clear child. The list would look flat when read with next, but each spliced parent would still point at the child list.
    • Forgetting the prev links. Reading forward works, but walking backward from the end breaks.
    • Losing the successor. If next is read after node!.next is overwritten, the rest of the level is lost.

    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