Flatten Multilevel List II in C++ (O(n) Time Complexity)


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.


Understanding the Problem

The core challenge of this problem is to flatten a multilevel doubly linked list into a single-level doubly linked list. This involves traversing through each node and ensuring that all child nodes are integrated into the main list in the correct order.

Common applications of this problem include scenarios where hierarchical data structures need to be linearized for easier processing or storage.

Potential pitfalls include missing nodes during traversal or incorrectly handling the child pointers, leading to an incorrect flattened list.

Approach

To solve this problem, we can use an iterative approach with the help of a stack. The stack will help us keep track of nodes that need to be processed later, ensuring that we correctly handle the multilevel structure.

1. **Naive Solution**: A naive solution might involve recursively traversing the list, but this is not optimal due to potential stack overflow issues with deep recursion.

2. **Optimized Solution**: An iterative approach using a stack is more efficient and avoids the pitfalls of recursion. This approach ensures that we process each node in O(n) time.

Algorithm

1. Initialize a stack and push the head of the list onto the stack.

2. While the stack is not empty, pop a node from the stack.

3. Process the current node: - If it has a next node, push the next node onto the stack. - If it has a child node, push the child node onto the stack and set the child pointer to null. - Update the previous and next pointers to maintain the doubly linked list structure.

4. Continue this process until the stack is empty.

Code Implementation

#include <iostream>
#include <stack>

struct Node {
    int val;
    Node* next;
    Node* prev;
    Node* child;
    Node(int _val) : val(_val), next(nullptr), prev(nullptr), child(nullptr) {}
};

Node* flatten(Node* head) {
    if (!head) return nullptr;

    std::stack<Node*> stack;
    stack.push(head);
    Node* prev = nullptr;

    while (!stack.empty()) {
        Node* curr = stack.top();
        stack.pop();

        if (prev) {
            prev->next = curr;
            curr->prev = prev;
        }

        if (curr->next) stack.push(curr->next);
        if (curr->child) {
            stack.push(curr->child);
            curr->child = nullptr;
        }

        prev = curr;
    }

    return head;
}

// Helper function to print the flattened list
void printList(Node* head) {
    while (head) {
        std::cout << head->val << " ";
        head = head->next;
    }
    std::cout << std::endl;
}

int main() {
    // Example usage
    Node* head = new Node(1);
    head->next = new Node(2);
    head->next->prev = head;
    head->next->next = new Node(3);
    head->next->next->prev = head->next;
    head->next->next->child = new Node(7);
    head->next->next->child->next = new Node(8);
    head->next->next->child->next->prev = head->next->next->child;

    Node* flattened = flatten(head);
    printList(flattened);

    return 0;
}

Complexity Analysis

The time complexity of the iterative approach is O(n), where n is the total number of nodes in the multilevel linked list. This is because each node is processed exactly once.

The space complexity is O(n) in the worst case due to the stack used to keep track of nodes.

Edge Cases

1. Empty list: The function should return null.

2. Single node with no children: The function should return the single node.

3. Deeply nested children: The function should correctly flatten all levels.

Testing

To test the solution comprehensively, consider the following test cases:

Thinking and Problem-Solving Tips

1. Break down the problem into smaller parts and solve each part step-by-step.

2. Use data structures like stacks to manage complex traversal scenarios.

3. Practice similar problems to improve problem-solving skills.

Conclusion

Flattening a multilevel doubly linked list is a common problem that tests your understanding of linked lists and iterative traversal techniques. By using a stack, we can efficiently flatten the list in O(n) time.

Understanding and solving such problems is crucial for developing strong problem-solving skills in data structures and algorithms.

Additional Resources