Skip to main content

Command Palette

Search for a command to run...

LeetCode 23: Merge k Sorted Lists

Updated
5 min read

OG: https://leetcode.com/problems/merge-k-sorted-lists/description/

import java.util.*;

/**
 * LeetCode 23: Merge k Sorted Lists
 * 
 * Problem: Merge k sorted linked lists and return it as one sorted list.
 * 
 * Example:
 * Input: lists = [[1,4,5],[1,3,4],[2,6]]
 * Output: [1,1,2,3,4,4,5,6]
 * 
 * Approaches implemented:
 * 1. Priority Queue (Min Heap) - O(N log k)
 * 2. Divide and Conquer - O(N log k)
 * 3. Sequential Merge - O(kN) [baseline for comparison]
 * 
 * where:
 * - N is total number of nodes across all lists
 * - k is number of linked lists
 */
public class MergeKSortedLists {

    /**
     * Definition for singly-linked list.
     */
    public static class ListNode {
        int val;
        ListNode next;
        ListNode(int x) { val = x; }
        ListNode(int x, ListNode next) { this.val = x; this.next = next; }
    }

    /**
     * Solution using Priority Queue (Min Heap)
     * 
     * Time Complexity: O(N log k)
     * - Each node is added/removed from heap once
     * - Heap operations cost O(log k)
     * - Total nodes = N
     * 
     * Space Complexity: O(k)
     * - Priority queue holds at most k nodes
     * 
     * @param lists Array of sorted linked lists
     * @return Merged sorted linked list
     */
    public ListNode mergeKListsPQ(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;

        // Create min heap comparing nodes by value
        PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);

        // Add first node from each list
        for (ListNode list : lists) {
            if (list != null) {
                pq.offer(list);
            }
        }

        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;

        // Process nodes from heap
        while (!pq.isEmpty()) {
            ListNode node = pq.poll();
            tail.next = node;
            tail = tail.next;

            if (node.next != null) {
                pq.offer(node.next);
            }
        }

        return dummy.next;
    }

    /**
     * Solution using Divide and Conquer
     * 
     * Time Complexity: O(N log k)
     * - Log k levels of merging
     * - Each level processes N nodes total
     * 
     * Space Complexity: O(1) extra space
     * - Only uses recursion stack
     * 
     * @param lists Array of sorted linked lists
     * @return Merged sorted linked list
     */
    public ListNode mergeKListsDC(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;
        return mergeDC(lists, 0, lists.length - 1);
    }

    private ListNode mergeDC(ListNode[] lists, int start, int end) {
        if (start == end) {
            return lists[start];
        }
        if (start + 1 == end) {
            return merge2Lists(lists[start], lists[end]);
        }

        int mid = start + (end - start) / 2;
        ListNode left = mergeDC(lists, start, mid);
        ListNode right = mergeDC(lists, mid + 1, end);
        return merge2Lists(left, right);
    }

    /**
     * Solution using Sequential Merging (baseline)
     * 
     * Time Complexity: O(kN)
     * - Merges lists one by one
     * - Not optimal but simple to understand
     * 
     * Space Complexity: O(1) extra space
     * 
     * @param lists Array of sorted linked lists
     * @return Merged sorted linked list
     */
    public ListNode mergeKListsSequential(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;

        ListNode result = lists[0];
        for (int i = 1; i < lists.length; i++) {
            result = merge2Lists(result, lists[i]);
        }
        return result;
    }

    /**
     * Helper method to merge two sorted lists
     */
    private ListNode merge2Lists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;

        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                tail.next = l1;
                l1 = l1.next;
            } else {
                tail.next = l2;
                l2 = l2.next;
            }
            tail = tail.next;
        }

        tail.next = (l1 != null) ? l1 : l2;
        return dummy.next;
    }

    /**
     * Helper method to create linked list from array
     */
    private static ListNode createList(int[] arr) {
        if (arr == null || arr.length == 0) return null;

        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int val : arr) {
            current.next = new ListNode(val);
            current = current.next;
        }
        return dummy.next;
    }

    /**
     * Helper method to convert linked list to string
     */
    private static String listToString(ListNode head) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        while (head != null) {
            sb.append(head.val);
            if (head.next != null) {
                sb.append(",");
            }
            head = head.next;
        }
        sb.append("]");
        return sb.toString();
    }

    public static void main(String[] args) {
        MergeKSortedLists solution = new MergeKSortedLists();

        // Test cases
        int[][][] testCases = {
            {{1,4,5}, {1,3,4}, {2,6}},                    // Standard case
            {{1}, {2}, {3}},                              // Single elements
            {{1,2,3}, {4,5,6}, {7,8,9}},                 // No interleaving
            {{1,1,1}, {1,1,1}, {1,1,1}},                 // All same values
            {{}, {}, {}},                                 // Empty lists
            {{-1,1}, {-2,2}, {-3,3}},                    // Negative numbers
            {{1,4,5}, {1,3,4}, {2,6}, {1,7}, {2,8}},    // More than 3 lists
            {{1,2,3,4,5,6,7,8,9,10}},                    // Single long list
            {},                                           // Empty array
            {{1,2}, {}, {3,4}},                          // Some empty lists
        };

        String[] testNames = {
            "Standard case",
            "Single elements",
            "No interleaving needed",
            "All same values",
            "Empty lists",
            "Negative numbers",
            "More than 3 lists",
            "Single long list",
            "Empty array",
            "Some empty lists"
        };

        for (int i = 0; i < testCases.length; i++) {
            test(solution, testCases[i], testNames[i]);
        }
    }

    private static void test(MergeKSortedLists solution, int[][] lists, String testName) {
        System.out.println("\nTest: " + testName);

        // Convert input arrays to ListNodes
        ListNode[] inputLists = new ListNode[lists.length];
        System.out.println("Input lists:");
        for (int i = 0; i < lists.length; i++) {
            inputLists[i] = createList(lists[i]);
            System.out.println("  " + listToString(inputLists[i]));
        }

        // Test all three approaches
        ListNode[] inputs = new ListNode[lists.length];

        // Priority Queue approach
        System.arraycopy(inputLists, 0, inputs, 0, lists.length);
        ListNode resultPQ = solution.mergeKListsPQ(inputs);
        System.out.println("Priority Queue result: " + listToString(resultPQ));

        // Divide and Conquer approach
        System.arraycopy(inputLists, 0, inputs, 0, lists.length);
        ListNode resultDC = solution.mergeKListsDC(inputs);
        System.out.println("Divide & Conquer result: " + listToString(resultDC));

        // Sequential approach
        System.arraycopy(inputLists, 0, inputs, 0, lists.length);
        ListNode resultSeq = solution.mergeKListsSequential(inputs);
        System.out.println("Sequential result: " + listToString(resultSeq));

        // Verify all approaches give same result
        boolean match = listToString(resultPQ).equals(listToString(resultDC)) &&
                       listToString(resultDC).equals(listToString(resultSeq));
        System.out.println("All solutions match: " + match);

        // Verify result is sorted
        boolean isSorted = isSorted(resultPQ);
        System.out.println("Result is sorted: " + isSorted);
    }

    /**
     * Helper method to verify list is sorted
     */
    private static boolean isSorted(ListNode head) {
        if (head == null || head.next == null) return true;

        ListNode current = head;
        while (current.next != null) {
            if (current.val > current.next.val) {
                return false;
            }
            current = current.next;
        }
        return true;
    }
}