- Think about how this problem is similar to flattening a tree
- What traversal order would help you process nodes in the correct sequence?
- Consider how to handle the connections between different levels
-
First, understand what "flattening" means:
- All nodes should be in a single level
- Child pointers should be nullified
- Nodes from child lists should be inserted between current node and next node
-
For each node:
- Process current node
- Handle its child list (if exists)
- Handle its next node
- Fix all prev/next pointers
For each root, when flattening:
childis flattened first.- Then the flattened child list should come before
next.
Idea!! It's like a tree preorder traversal, we just relink the nodes during traversal.
Example visualization:
1 -> 5 -> ...
|
2 -> 3 -> 4
flatten(1) = 1 -> 2 -> 3 -> 4 -> 5 -> ...The key idea is to use DFS to process nodes in preorder fashion, where we flatten child list first before connecting it with the next nodes.
current -> next
|
child -> ...
|
...
// Start flattening from current node.
current -> f(next)
|
f(child) -> ...
|
...
flatten(current) =
current -> f(child) -> f(next)
// And so on...
// Example:
2 -> 3
|
5 -> 6
|
7 -> 8 -> 9
f(2) =
2 -> f(5) -> f(3)
f(5) =
5 -> f(7) -> f(6)
// Backtracking...
f(7) = 7 -> 8 -> 9
f(5) = 5 -> 7 -> 8 -> 9 -> f(6)fun flatten(root: Node?): Node? {
if (root == null) return null
val next = root.next
val prev = root.prev
val child = root.child
val flattenedNext = flatten(next)
if (child != null) {
val flattenedChild = flatten(child)
root?.child = null
// Connect the last node of the child list to the next node.
// See f(5) = 5 -> 7 -> 8 -> 9 -> 6, we need to connect 9 to 6.
var last = flattenedChild
while (last?.next != null) {
last = last.next
}
last?.next = flattenedNext
flattenedNext?.prev = last
root.next = flattenedChild
flattenedChild?.prev = root
}
return root
}- Time Complexity:
O(N)whereNis the total number of nodes - Space Complexity:
O(H)whereHis the maximum depth of the multilevel structure (recursion stack)
The key idea is to use a stack to simulate the recursive process, pushing next and child nodes in reverse order to maintain proper sequence.
迭代就是一層一層移上來,節點有 child 就先往 stack 送,等遍歷完子節點後再從棧取出回朔。
fun flatten(root: Node?): Node? {
if (root == null) return null
val sentinel = Node(-1)
var current = sentinel
val stack = Stack<Node>()
stack.push(root)
while (stack.isNotEmpty()) {
val node = stack.pop()
current.next = node
node.prev = current
if (node.next != null) {
stack.push(node.next)
}
if (node.child != null) {
stack.push(node.child)
node.child = null
}
current = current.next!!
}
/**
* Remember to remove the sentinel node.
* The real `prev` of the first node should be null, but we
* will update it while iterating:
* ```
* current.next = node
* node.prev = current
* ```
* After flattening, the list looks like:
* `sentinel <- -> flattened list`
* so we have to "reset" it.
*/
sentinel.next?.prev = null
return sentinel.next
}- Time Complexity:
O(N)whereNis the total number of nodes - Space Complexity:
O(N)in worst case when all nodes are in child lists
The key idea is to flatten one child level by level, and then connect it with the next level.
current -> next -> ...
|
child 1 -> next 1 -> ... tail 1
|
child 2 -> next 2 -> ... tail 2
// Flatten child 1
current -> child 1 -> next 1 -> ... tail 1 -> next -> ...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // child 1 is flattened
|
child 2 -> next 2 -> ... tail 2
// We move `current` to `current.next`, which is `child 1`:
current -> child 1 (new current) -> ...
|
child 2 -> next 2 -> ... tail 2
// Flatten child 2
current -> child 1 -> child 2 -> next 2 -> ... tail 2 -> next 1 -> ... tail 1 -> next -> ...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // child 2 is flattenedfun flatten(root: Node?): Node? {
var current = root
while (current != null) {
if (current.child != null) {
val next = current.next
val child = current.child!!
current.next = child
child.prev = current
current.child = null
var tail = current!!
while (tail.next != null) {
tail = tail.next!!
}
tail.next = next
next?.prev = tail
}
// current.next is the new node after flattening, which is the child node if it exists or the next node otherwise.
current = current.next
}
return root
}- Time Complexity:
O(N)whereNis the total number of nodes - Space Complexity:
O(1)
- Single node with no
child - Node with
childbut nonext - Node with both
childandnext - Multiple levels of children
Visual examples for tricky edge cases:
// Case 2: Node with child but no next
1
|
2 -> 3
|
4
// Should become: 1 -> 2 -> 3 -> 4
// Case 3: Node with both child and next
1 -> 2 -> 3
|
4 -> 5
|
6
// Should become: 1 -> 4 -> 5 -> 6 -> 2 -> 3
// Case 4: Multiple levels of children
1 -> 2
|
3
|
4 -> 5
|
6
// Should become: 1 -> 3 -> 4 -> 5 -> 6 -> 2- Forgetting to nullify child pointers after flattening
- Not properly handling prev pointers when connecting lists