diff --git a/src/main/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedList.java new file mode 100644 index 000000000000..3c4106f178b9 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedList.java @@ -0,0 +1,92 @@ +package com.thealgorithms.datastructures.lists; +/** + * Implements an algorithm to flatten a multilevel linked list. + * + * In this specific problem structure, each node has a `next` pointer (to the + * next node at the same level) and a `child` pointer (which points to the head + * of another sorted linked list). The goal is to merge all these lists into a + * single, vertically sorted linked list using the `child` pointer. + * + * The approach is a recursive one that leverages a merge utility, similar to + * the merge step in Merge Sort. It recursively flattens the list starting from + * the rightmost node and merges each node's child list with the already + * flattened list to its right. + * @see GeeksforGeeks: Flattening a Linked List + */ +public final class FlattenMultilevelLinkedList { + /** + * Private constructor to prevent instantiation of this utility class. + */ + private FlattenMultilevelLinkedList() { + } + /** + * Node represents an element in the multilevel linked list. It contains the + * integer data, a reference to the next node at the same level, and a + * reference to the head of a child list. + */ + static class Node { + int data; + Node next; + Node child; + + Node(int data) { + this.data = data; + this.next = null; + this.child = null; + } + } + + /** + * Merges two sorted linked lists (connected via the `child` pointer). + * This is a helper function for the main flatten algorithm. + * + * @param a The head of the first sorted list. + * @param b The head of the second sorted list. + * @return The head of the merged sorted list. + */ + private static Node merge(Node a, Node b) { + // If one of the lists is empty, return the other. + if (a == null) { + return b; + } + if (b == null) { + return a; + } + + Node result; + + // Choose the smaller value as the new head. + if (a.data < b.data) { + result = a; + result.child = merge(a.child, b); + } else { + result = b; + result.child = merge(a, b.child); + } + result.next = null; // Ensure the merged list has no `next` pointers. + return result; + } + + /** + * Flattens a multilevel linked list into a single sorted list. + * The flattened list is connected using the `child` pointers. + * + * @param head The head of the top-level list (connected via `next` pointers). + * @return The head of the fully flattened and sorted list. + */ + public static Node flatten(Node head) { + // Base case: if the list is empty or has only one node, it's already flattened. + if (head == null || head.next == null) { + return head; + } + + // Recursively flatten the list starting from the next node. + head.next = flatten(head.next); + + // Now, merge the current list (head's child list) with the flattened rest of the list. + head = merge(head, head.next); + + // Return the head of the fully merged list. + return head; + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedListTest.java new file mode 100644 index 000000000000..667f9fcf5700 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedListTest.java @@ -0,0 +1,112 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +/** + * Unit tests for the FlattenMultilevelLinkedList class. + * This class tests the flattening logic with various list structures, + * including null lists, simple lists, and complex multilevel lists. + */ +final class FlattenMultilevelLinkedListTest { + + // A helper function to convert a flattened list (connected by child pointers) + // into a standard Java List for easy comparison. + private List toList(FlattenMultilevelLinkedList.Node head) { + List list = new ArrayList<>(); + FlattenMultilevelLinkedList.Node current = head; + while (current != null) { + list.add(current.data); + current = current.child; + } + return list; + } + + @Test + @DisplayName("Test with a null list") + void testFlattenNullList() { + assertNull(FlattenMultilevelLinkedList.flatten(null)); + } + + @Test + @DisplayName("Test with a simple, single-level list") + void testFlattenSingleLevelList() { + // Create a simple list: 1 -> 2 -> 3 + FlattenMultilevelLinkedList.Node head = new FlattenMultilevelLinkedList.Node(1); + head.next = new FlattenMultilevelLinkedList.Node(2); + head.next.next = new FlattenMultilevelLinkedList.Node(3); + + // Flatten the list + FlattenMultilevelLinkedList.Node flattenedHead = FlattenMultilevelLinkedList.flatten(head); + + // Expected output: 1 -> 2 -> 3 (vertically) + List expected = List.of(1, 2, 3); + assertEquals(expected, toList(flattenedHead)); + } + + @Test + @DisplayName("Test with a complex multilevel list") + void testFlattenComplexMultilevelList() { + // Create the multilevel structure from the problem description + // 5 -> 10 -> 19 -> 28 + // | | | | + // 7 20 22 35 + // | | | + // 8 50 40 + // | | + // 30 45 + FlattenMultilevelLinkedList.Node head = new FlattenMultilevelLinkedList.Node(5); + head.child = new FlattenMultilevelLinkedList.Node(7); + head.child.child = new FlattenMultilevelLinkedList.Node(8); + head.child.child.child = new FlattenMultilevelLinkedList.Node(30); + + head.next = new FlattenMultilevelLinkedList.Node(10); + head.next.child = new FlattenMultilevelLinkedList.Node(20); + + head.next.next = new FlattenMultilevelLinkedList.Node(19); + head.next.next.child = new FlattenMultilevelLinkedList.Node(22); + head.next.next.child.child = new FlattenMultilevelLinkedList.Node(50); + + head.next.next.next = new FlattenMultilevelLinkedList.Node(28); + head.next.next.next.child = new FlattenMultilevelLinkedList.Node(35); + head.next.next.next.child.child = new FlattenMultilevelLinkedList.Node(40); + head.next.next.next.child.child.child = new FlattenMultilevelLinkedList.Node(45); + + // Flatten the list + FlattenMultilevelLinkedList.Node flattenedHead = FlattenMultilevelLinkedList.flatten(head); + + // Expected sorted output + List expected = List.of(5, 7, 8, 10, 19, 20, 22, 28, 30, 35, 40, 45, 50); + assertEquals(expected, toList(flattenedHead)); + } + + @Test + @DisplayName("Test with some empty child lists") + void testFlattenWithEmptyChildLists() { + // Create a list: 5 -> 10 -> 12 + // | | + // 7 11 + // | + // 9 + FlattenMultilevelLinkedList.Node head = new FlattenMultilevelLinkedList.Node(5); + head.child = new FlattenMultilevelLinkedList.Node(7); + head.child.child = new FlattenMultilevelLinkedList.Node(9); + + head.next = new FlattenMultilevelLinkedList.Node(10); // No child list + head.next.child = null; + + head.next.next = new FlattenMultilevelLinkedList.Node(12); + head.next.next.child = new FlattenMultilevelLinkedList.Node(16); + + // Flatten the list + FlattenMultilevelLinkedList.Node flattenedHead = FlattenMultilevelLinkedList.flatten(head); + + // Expected sorted output + List expected = List.of(5, 7, 9, 10, 12, 16); + assertEquals(expected, toList(flattenedHead)); + } +}