You should aim for a solution as good or better than O(n * k) time and O(1) space, where k is the total number of lists and n is the total number of nodes across all lists.
Hint 1
A brute-force solution would involve storing all n nodes in an array, sorting them, and converting the array back into a linked list, resulting in an O(nlogn) time complexity. Can you think of a better way? Perhaps consider leveraging the idea behind merging two sorted linked lists.
Hint 2
We can merge two sorted linked lists without using any extra space. To handle k sorted linked lists, we can iteratively merge each linked list with a resultant merged list. How can you implement this?
Hint 3
We iterate through the list array with index i, starting at i = 1. We merge the linked lists using mergeTwoLists(lists[i], lists[i - 1]), which returns the head of the merged list. This head is stored in lists[i], and the process continues. Finally, the merged list is obtained at the last index, and we return its head.
Company Tags
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Linked Lists - Understanding singly linked list structure, traversal, and node manipulation
Merge Two Sorted Lists - The fundamental operation used repeatedly in all approaches
Min Heap / Priority Queue - Used to efficiently find the smallest element among k lists
Divide and Conquer - Strategy to recursively split and merge lists in pairs
Sorting - Used in the brute force approach to sort all collected values
1. Brute Force
Intuition
The simplest way to merge all linked lists is to ignore the list structure, collect every value, sort them, and then rebuild a single sorted linked list. This doesn't use any clever merging logic — it is purely based on gathering and sorting. It's easy to implement but not efficient because sorting dominates the runtime.
Algorithm
Create an empty list nodes.
For each linked list:
Traverse it and append every node's value to nodes.
Sort the nodes list.
Create a new linked list:
Use a dummy head.
For each value in the sorted list, create a new node and attach it.
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/publicclassSolution{publicListNodeMergeKLists(ListNode[] lists){if(lists.Length ==0)returnnull;for(int i =1; i < lists.Length; i++){
lists[i]=Merge(lists[i], lists[i -1]);}return lists[lists.Length -1];}privateListNodeMerge(ListNode l1,ListNode l2){ListNode dummy =newListNode(0);ListNode curr = dummy;while(l1 !=null&& l2 !=null){if(l1.val <= l2.val){
curr.next = l1;
l1 = l1.next;}else{
curr.next = l2;
l2 = l2.next;}
curr = curr.next;}if(l1 !=null){
curr.next = l1;}else{
curr.next = l2;}return dummy.next;}}
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/funcmergeList(l1 *ListNode, l2 *ListNode)*ListNode {
dummy :=&ListNode{}
tail := dummy
for l1 !=nil&& l2 !=nil{if l1.Val < l2.Val {
tail.Next = l1
l1 = l1.Next
}else{
tail.Next = l2
l2 = l2.Next
}
tail = tail.Next
}if l1 !=nil{
tail.Next = l1
}if l2 !=nil{
tail.Next = l2
}return dummy.Next
}funcmergeKLists(lists []*ListNode)*ListNode {iflen(lists)==0{returnnil}for i :=1; i <len(lists); i++{
lists[i]=mergeList(lists[i-1], lists[i])}return lists[len(lists)-1]}
/**
* Example:
* var li = ListNode(5)
* var v = li.`val`
* Definition for singly-linked list.
* class ListNode(var `val`: Int) {
* var next: ListNode? = null
* }
*/class Solution {privatefunmergeList(l1: ListNode?, l2: ListNode?): ListNode?{val dummy =ListNode(0)var tail = dummy
var first = l1
var second = l2
while(first !=null&& second !=null){if(first.`val` < second.`val`){
tail.next = first
first = first.next
}else{
tail.next = second
second = second.next
}
tail = tail.next!!}if(first !=null){
tail.next = first
}if(second !=null){
tail.next = second
}return dummy.next
}funmergeKLists(lists: Array<ListNode?>): ListNode?{if(lists.isEmpty()){returnnull}for(i in1 until lists.size){
lists[i]=mergeList(lists[i-1], lists[i])}return lists.last()}}
/**
* Definition for singly-linked list.
* public class ListNode {
* public var val: Int
* public var next: ListNode?
* public init() { self.val = 0; self.next = nil; }
* public init(_ val: Int) { self.val = val; self.next = nil; }
* public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
* }
*/classSolution{funcmergeKLists(_ lists:[ListNode?])->ListNode?{if lists.isEmpty {returnnil}var lists = lists
for i in1..<lists.count {
lists[i]=mergeList(lists[i -1], lists[i])}return lists.last!}privatefuncmergeList(_ l1:ListNode?,_ l2:ListNode?)->ListNode?{let dummy =ListNode(0)var tail = dummy
var l1 = l1, l2 = l2
while l1 !=nil&& l2 !=nil{if l1!.val < l2!.val {
tail.next = l1
l1 = l1?.next
}else{
tail.next = l2
l2 = l2?.next
}
tail = tail.next!}if l1 !=nil{
tail.next = l1
}if l2 !=nil{
tail.next = l2
}return dummy.next
}}
Time & Space Complexity
Time complexity: O(n∗k)
Space complexity: O(1)
Where k is the total number of lists and n is the total number of nodes across k lists.
4. Heap
Intuition
We want to always pick the smallest current node among all k lists as efficiently as possible.
Instead of scanning all heads every time (which is slow), we can use a min-heap (priority queue):
Push the head of each non-empty list into the heap.
The heap always gives us the node with the smallest value on top.
We pop that node, attach it to our result list, and then push its next node (if it exists) into the heap.
Repeat until the heap is empty.
This way, at every step we choose the globally smallest node in O(log k) time, where k is the number of lists.
Algorithm
Create a min-heap (priority queue).
For each non-empty linked list:
Push its head node into the heap (keyed by node value).
Create a dummy node to start the result list, and a pointer cur pointing to it.
While the heap is not empty:
Pop the node with the smallest value from the heap.
Attach this node to cur.next, and move cur forward.
If this node has a next node, push that next node into the heap.
When the heap is empty, all nodes have been merged in sorted order.
/**
* Definition for singly-linked list.
* class ListNode {
* constructor(val = 0, next = null) {
* this.val = val;
* this.next = next;
* }
* }
*/classSolution{/**
* @param {ListNode[]} lists
* @return {ListNode}
*/mergeKLists(lists){if(lists.length ===0)returnnull;const minHeap =newMinPriorityQueue((x)=> x.val);for(let list of lists){if(list !=null) minHeap.enqueue(list);}let res =newListNode(0);let cur = res;while(minHeap.size()>0){let node = minHeap.dequeue();
cur.next = node;
cur = cur.next;
node = node.next;if(node !=null){
minHeap.enqueue(node);}}return res.next;}}
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/publicclassSolution{publicListNodeMergeKLists(ListNode[] lists){if(lists.Length ==0)returnnull;var minHeap =newPriorityQueue<ListNode,int>();foreach(var list in lists){if(list !=null){
minHeap.Enqueue(list, list.val);}}var res =newListNode(0);var cur = res;while(minHeap.Count >0){var node = minHeap.Dequeue();
cur.next = node;
cur = cur.next;
node = node.next;if(node !=null){
minHeap.Enqueue(node, node.val);}}return res.next;}}
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/funcmergeKLists(lists []*ListNode)*ListNode {iflen(lists)==0{returnnil}
minHeap := priorityqueue.NewWith(func(a, b interface{})int{return a.(*ListNode).Val - b.(*ListNode).Val
})for_, list :=range lists {if list !=nil{
minHeap.Enqueue(list)}}
res :=&ListNode{Val:0}
cur := res
for!minHeap.Empty(){
node,_:= minHeap.Dequeue()
cur.Next = node.(*ListNode)
cur = cur.Next
if cur.Next !=nil{
minHeap.Enqueue(cur.Next)}}return res.Next
}
/**
* Example:
* var li = ListNode(5)
* var v = li.`val`
* Definition for singly-linked list.
* class ListNode(var `val`: Int) {
* var next: ListNode? = null
* }
*/class Solution {funmergeKLists(lists: Array<ListNode?>): ListNode?{if(lists.isEmpty())returnnullval minHeap = PriorityQueue<ListNode>(compareBy { it.`val` })for(list in lists){
list?.let{ minHeap.offer(it)}}val res =ListNode(0)var cur = res
while(minHeap.isNotEmpty()){val node = minHeap.poll()
cur.next = node
cur = cur.next!!
node.next?.let{ minHeap.offer(it)}}return res.next
}}
/**
* Definition for singly-linked list.
* public class ListNode {
* public var val: Int
* public var next: ListNode?
* public init() { self.val = 0; self.next = nil; }
* public init(_ val: Int) { self.val = val; self.next = nil; }
* public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
* }
*/structNodeWrapper:Comparable{let node:ListNodeinit(_ node:ListNode){self.node = node
}staticfunc<(lhs:NodeWrapper, rhs:NodeWrapper)->Bool{return lhs.node.val < rhs.node.val
}staticfunc==(lhs:NodeWrapper, rhs:NodeWrapper)->Bool{return lhs.node.val == rhs.node.val
}}classSolution{funcmergeKLists(_ lists:[ListNode?])->ListNode?{var heap =Heap<NodeWrapper>()for list in lists {iflet node = list {
heap.insert(NodeWrapper(node))}}let dummy =ListNode(0)var tail = dummy
whilelet wrapper = heap.popMin(){
tail.next = wrapper.node
tail = tail.next!iflet next = wrapper.node.next {
heap.insert(NodeWrapper(next))}}return dummy.next
}}
Time & Space Complexity
Time complexity: O(nlogk)
Space complexity: O(k)
Where k is the total number of lists and n is the total number of nodes across k lists.
5. Divide And Conquer (Recursion)
Intuition
Instead of merging all k lists at once or one by one in order, we can use a divide and conquer strategy, similar to how merge sort works.
Idea:
Split the list of linked lists into two halves.
Recursively merge the left half into one sorted list.
Recursively merge the right half into one sorted list.
Finally, merge these two sorted lists into a single sorted list.
By always merging pairs of lists, we reduce the total work compared to merging k lists sequentially. Each merge of two lists is linear in their total length, and we do about log k levels of merging.
This makes the approach both clean and efficient.
Algorithm
Base Cases
If the input list lists is empty, return null.
Use a recursive function divide(lists, l, r):
If l > r, return null.
If l == r, return lists[l] (only one list to return).
Divide Step
Compute mid = (l + r) // 2.
Recursively compute:
left = divide(lists, l, mid)
right = divide(lists, mid + 1, r)
Conquer Step (merge two lists)
Merge left and right using the standard merge two sorted linked lists routine:
Create a dummy node and use a curr pointer.
While both lists are non-empty:
Attach the smaller node to curr.next.
Move that list's pointer forward.
Move curr forward.
Attach any remaining nodes from either list.
Return the merged list.
Final Answer
Call divide(lists, 0, len(lists) - 1) and return the resulting list.
This approach efficiently merges k sorted linked lists in a structured, recursive way.
/**
* Definition for singly-linked list.
* public class ListNode {
* public var val: Int
* public var next: ListNode?
* public init() { self.val = 0; self.next = nil; }
* public init(_ val: Int) { self.val = val; self.next = nil; }
* public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
* }
*/classSolution{funcmergeKLists(_ lists:[ListNode?])->ListNode?{if lists.isEmpty {returnnil}returndivide(lists,0, lists.count -1)}privatefuncdivide(_ lists:[ListNode?],_ l:Int,_ r:Int)->ListNode?{if l > r {returnnil}if l == r {return lists[l]}let mid = l +(r - l)/2letleft=divide(lists, l, mid)letright=divide(lists, mid +1, r)returnconquer(left,right)}privatefuncconquer(_ l1:ListNode?,_ l2:ListNode?)->ListNode?{let dummy =ListNode(0)var curr = dummy
var l1 = l1, l2 = l2
whilelet node1 = l1,let node2 = l2 {if node1.val <= node2.val {
curr.next = node1
l1 = node1.next
}else{
curr.next = node2
l2 = node2.next
}
curr = curr.next!}
curr.next = l1 ?? l2
return dummy.next
}}
Time & Space Complexity
Time complexity: O(nlogk)
Space complexity: O(logk)
Where k is the total number of lists and n is the total number of nodes across k lists.
6. Divide And Conquer (Iteration)
Intuition
This is the same idea as divide and conquer, but done iteratively instead of using recursion.
We repeatedly merge the lists in pairs:
In one pass:
Merge list 0 and list 1 -> get M0
Merge list 2 and list 3 -> get M1
Merge list 4 and list 5 -> get M2
... and so on.
After this pass, we have fewer lists (about half as many).
Repeat this process on the new list of merged lists until only one list remains.
Each pairwise merge is just the usual merge of two sorted linked lists. By always merging lists two at a time, the total work is efficient and structured, similar to the merge step in merge sort.
Algorithm
If lists is empty, return null.
While the number of lists is greater than 1:
Create an empty list mergedLists.
Loop over lists in steps of 2:
Let l1 = lists[i].
Let l2 = lists[i + 1] if it exists, otherwise None.
Merge l1 and l2 using mergeList and append the result to mergedLists.
Set lists = mergedLists.
When the loop ends, lists[0] is the fully merged sorted list. Return it.
Merging two lists (mergeList(l1, l2)):
Create a dummy node and a tail pointer pointing to it.
While both l1 and l2 are non-empty:
Attach the smaller of l1 and l2 to tail.next.
Move the chosen list's pointer forward.
Move tail forward.
Attach any remaining nodes from l1 or l2 to tail.next.
/**
* Definition for singly-linked list.
* public class ListNode {
* public var val: Int
* public var next: ListNode?
* public init() { self.val = 0; self.next = nil; }
* public init(_ val: Int) { self.val = val; self.next = nil; }
* public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
* }
*/classSolution{funcmergeKLists(_ lists:[ListNode?])->ListNode?{if lists.isEmpty {returnnil}var lists = lists
while lists.count >1{var mergedLists:[ListNode?]=[]for i instride(from:0, to: lists.count, by:2){let l1 = lists[i]let l2 = i +1< lists.count ? lists[i +1]:nil
mergedLists.append(mergeList(l1, l2))}
lists = mergedLists
}return lists[0]}privatefuncmergeList(_ l1:ListNode?,_ l2:ListNode?)->ListNode?{let dummy =ListNode(0)var tail = dummy
var l1 = l1, l2 = l2
whilelet node1 = l1,let node2 = l2 {if node1.val < node2.val {
tail.next = node1
l1 = node1.next
}else{
tail.next = node2
l2 = node2.next
}
tail = tail.next!}
tail.next = l1 ?? l2
return dummy.next
}}
Time & Space Complexity
Time complexity: O(nlogk)
Space complexity: O(k)
Where k is the total number of lists and n is the total number of nodes across k lists.
Common Pitfalls
Not Handling Empty Lists in the Input Array
The input array lists may contain null or empty linked lists. Failing to check for these before accessing node values will cause null pointer exceptions. Always verify that a list is non-empty before processing it.
Forgetting to Advance the Chosen List's Pointer
After selecting the smallest node from among the k lists, you must move that list's head pointer to the next node. Forgetting this step causes an infinite loop where the same node is repeatedly selected.
Incorrect Comparator for Min-Heap
When using a priority queue or min-heap, the comparator must compare node values correctly. In some languages, the default heap is a max-heap (like Python's heapq with negative values or C++'s priority_queue). Using the wrong comparison direction results in a max-heap instead of a min-heap, producing an incorrectly sorted output.
Not Using a Dummy Node for the Result List
Building the result list without a dummy head node requires special handling for the first node and complicates the logic. Using a dummy node simplifies the code by allowing uniform treatment of all nodes, then returning dummy.next as the result.
Modifying the Input Lists Array During Iteration
When merging lists one by one or in the divide-and-conquer approach, be careful about how you update the lists array. Overwriting elements while still iterating can lead to incorrect merges or skipped lists. Either use a separate array for merged results or iterate carefully with proper indexing.