24 - Merge K Sorted Linked Lists
Problem Statement¶
You are given an array of \(k\) linked lists lists, where each list is sorted in ascending order. Return the sorted linked list that is the result of merging all of the individual linked lists.
Examples¶
Example 1¶
Input: lists = [[1,2,4],[1,3,5],[3,6]]
Output: [1,1,2,3,3,4,5,6]
Explanation: Merging all lists into one sorted list.
Example 2¶
Input: lists = []
Output: []
Example 3¶
Input: lists = [[]]
Output: []
Solution¶
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
// Min-Heap to store the current nodes of each list
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
// 1. Initial push: add the head of each non-empty list
for (ListNode list : lists) {
if (list != null) {
pq.add(list);
}
}
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
// 2. Extract min and push next node
while (!pq.isEmpty()) {
ListNode node = pq.poll();
curr.next = node;
curr = curr.next;
if (node.next != null) {
pq.add(node.next);
}
}
return dummy.next;
}
}
Intuition¶
To merge \(k\) sorted lists, we need an efficient way to find the smallest element among \(k\) candidates (one from each list) at every step. 1. A Priority Queue (Min-Heap) is the ideal data structure here. It keeps the smallest node at the top. 2. We start by adding the head of every list to the heap. 3. In each iteration, we extract the smallest node from the heap, append it to our result list, and add the next node from the same list into the heap. 4. This ensures that the heap always contains the "potential candidates" for the next smallest value in the global sorted sequence.
Java Collections Framework Component Used¶
PriorityQueue¶
- What it is: An unbounded priority queue based on a priority heap. The elements of the priority queue are ordered according to their natural ordering, or by a
Comparator. - Why it is used: It provides \(O(\log k)\) time for both insertion and extraction of the minimum element. For \(k\) lists, a simple scan for the minimum would take \(O(k)\) per node, leading to \(O(N \cdot k)\) complexity. The heap reduces this to \(O(N \log k)\).
- How it is used in the solution:
- Initialized with a custom
Comparatorto compareListNode.val. pq.add(node): Adds a node to the heap.pq.poll(): Retrieves and removes the smallest node.
- Initialized with a custom
Complexity Analysis¶
- Time Complexity: \(O(N \log k)\), where \(N\) is the total number of nodes across all lists and \(k\) is the number of linked lists. Each of the \(N\) nodes is added to and removed from the heap once.
- Space Complexity: \(O(k)\), as the priority queue stores at most one node from each of the \(k\) lists at any given time.
Key Takeaways¶
- Multiway Merge: This problem is a classic example of merging multiple sorted streams, a concept also used in External Sorting.
- Heap Utility: Priority queues are indispensable when you need to frequently access the minimum or maximum element in a dynamic set of data.
- Edge Case Handling: Always check if the input array is empty or contains only null/empty lists to avoid null pointer exceptions. humanitarian