Problem overview
The method flatten receives the head of a doubly linked list whose nodes (Node) have three links: prev, next and child. A child may point to the head of a separate doubly linked list, and the nodes of that list can have children of their own, so the structure has several levels. The method must flatten it into one level, in place, and return the head.
In the first 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. The flattened list is [1, 2, 3, 7, 8, 11, 12, 9, 10, 4, 5, 6]. Each child list is inserted right after its parent, before the parent's old next. In the second example, 1-2 with a child 3 under node 1 becomes [1, 3, 2]. An empty list stays empty.
Approach: flatten the deepest levels first
Walk along one level. When a node has a child, three things must happen: the child list becomes the node's next, the end of the child list is joined to the node's old next, and the node's child is cleared.
The child list may itself contain nodes with children. The simplest way to handle that is to flatten the child list first, with the same method, and then splice the result in. A recursive helper, flattenLevel, does this. It flattens the level that starts at the node it receives and returns the tail of the flattened result, the last node. The caller needs that tail to reconnect the rest of the parent level.
Because every splice happens in place, the first node of the list never changes, so flatten returns the same head it was given.
Step by step
- Delegate to a helper.
flattencallsflattenLevel(head), ignores the returned tail, and ends withhead. - Declare the helper before
flatten. It takes aNodenamednode, the first node of a level, and returns aNode, the tail of the flattened level. - Copy the parameter into variables. Parameters can't be reassigned, so
var curwalks the level andvar tailremembers the last node visited. Both start atnode. For an empty list, the loop never runs and the helper returnsnull. - Loop while
cur != null, so every node on this level is visited. - Check for a child with
cur.child != null. Nodes without one need nothing. - Save
cur.nextinval next. Oncecur.nextpoints to the child, the old successor would otherwise be lost. - Flatten the child list recursively and keep the tail it returns in
val childTail. The recursive call handles all deeper levels. - Link
curto its child in both directions:cur.next = cur.child, thencur.child.prev = cur. Both lines readcur.child, so they must run before it is cleared. - Clear the child pointer:
cur.child = null. A flattened list has no child links left. - Reconnect the saved rest of the level. Set
childTail.next = next, and only whennextis notnull, setnext.prev = childTail. - Record and advance. After the
if, settail = curandcur = cur.next. After a splice,cur.nextis the child, so the loop walks through the spliced nodes too, andtailends up on the true last node. - Return
tailafter the loop. This is what the recursive call stores inchildTail.
The solution in Scala
class Solution {
def flattenLevel(node: Node): Node = {
var cur = node
var tail = node
while (cur != null) {
if (cur.child != null) {
val next = cur.next
val childTail = flattenLevel(cur.child)
cur.next = cur.child
cur.child.prev = cur
cur.child = null
childTail.next = next
if (next != null) {
next.prev = childTail
}
}
tail = cur
cur = cur.next
}
tail
}
def flatten(head: Node): Node = {
flattenLevel(head)
head
}
}
The Scala details worth knowing:
varandval.curandtailchange as the loop runs, so they arevar.nextandchildTailare assigned once per splice, so they areval, and each loop iteration that splices gets fresh ones.- A recursive method needs a result type. Scala requires the return type to be written on a recursive method, which is why the signature says
: Node. The compiler can't infer it from a body that calls itself. - The last expression is the value. The helper ends with
tailandflattenends withhead, with noreturn. The callflattenLevel(head)on its own line is an expression whose value is simply discarded. - Assigning through a chain.
cur.child.prev = curreads the fieldchildand then assigns toprevon that node. The assignment is allowed becauseprevis avarfield ofNode. - The null check on
next.nullis a valid value forNode, and writingnext.prevwhennextisnullwould throw aNullPointerException.
Complexity
Time: O(n), where n is the total number of nodes. Every node is visited by exactly one loop: the loop of the level where it started, or, after a splice, the loop of the level it was spliced into. Splicing is constant work per child.
Space: O(d), where d is the deepest nesting of child lists, for the recursion stack. The list itself is rewired in place and no nodes are created. A very deep chain of children could in principle overflow the stack.
Edge cases and common mistakes
- Empty list.
head = nullmakes the loop skip at once, andflattenreturnsnull. - A child on the last node. Then
nextisnull. The child list just becomes the end of the list, and the guard prevents a null dereference. - Forgetting to clear
child. The result looks right when printed throughnextlinks, but nodes still carry child pointers, so the list is not truly single-level. - Losing the rest of the level. Overwriting
cur.nextbefore saving it cuts off nodes 4, 5 and 6 in the first example. - Not setting both directions. Updating only
nextand leavingprevstale gives a list that can be walked forward but is broken when walked backward. - Using the wrong tail. Reconnecting the old
nextto the child's head, instead of to the end of the flattened child list, would drop everything after the first node of the child. - Returning
cur. After the loop it isnull. The helper returnstail, andflattenreturnshead.
Testing
- The first example,
1-2-3-4-5-6with children under 3 (7-8-9-10) and under 8 (11-12), gives[1, 2, 3, 7, 8, 11, 12, 9, 10, 4, 5, 6]. 1-2with a child3under node 1 gives[1, 3, 2].- An empty list gives an empty list.
1-2-3with a child4under node 1 and a child5under node 2 gives[1, 4, 2, 5, 3].- Walking the flattened first example backward through
prevfrom its last node gives[6, 5, 4, 10, 9, 12, 11, 8, 7, 3, 2, 1].
After flattening the multilevel linked list it becomes:
