Problem overview
mergeKLists(lists []*ListNode) receives a slice of k linked lists, each sorted in ascending order, and returns one sorted linked list holding all their values. A list node is a struct with a Val field and a Next pointer, and a list is represented by its first node, with nil standing for an empty list. The slice itself can be empty, and some or all of its lists can be empty.
For lists = [[1,4,5],[1,3,4],[2,6]] the merged list is 1->1->2->3->4->4->5->6. For lists = [] and for lists = [[]] the answer is the empty list, nil.
Approach: always take the smallest head
Each input list is already sorted, so its smallest unused value is always at its front. The smallest unused value overall is therefore the smallest of the k front values. Take that one, put it at the end of the answer, and move that list forward by one node. Repeat until every list is used up.
Finding the smallest front value is the simplest possible step: look at all k heads and keep the best. That scan costs O(k), and it happens once for each of the n values in the output, so the total is O(n * k), where n is the number of nodes across all lists. It is a brute-force method. A min-heap finds the smallest head in O(log k) instead, which brings the total down to O(n log k), and that has its own lesson.
To build the answer, the solution uses a dummy node placed before the real head, plus a tail pointer that always refers to the last node appended. Because tail starts on the dummy, appending the first value is no different from appending any other, and the finished list starts at dummy.Next.
Step by step
- Create the dummy node with
dummy := &ListNode{}. Its value is never used. - Create the walker
tail := dummy. It always points at the last node of the merged list. - Start an infinite loop with
for { ... }. Go has nowhilekeyword, and a bareforis the usual way to write a loop that ends withbreak. - Declare
ind := -1at the top of each pass. It will hold the position of the list with the smallest head, and -1 means that none has been chosen yet. - Scan all the lists with
for i := 0; i < len(lists); i++. - Update the best choice. If
lists[i]is notniland either nothing is chosen yet or its head value is strictly smaller than the chosen one's, setind = i. - Stop when the scan finds nothing. If
indis still -1, every list is empty, sobreak. - Append a new node with the smallest value:
tail.Next = &ListNode{Val: lists[ind].Val}. - Advance the chosen list with
lists[ind] = lists[ind].Next. - Move
tailonto the node just appended. - Return
dummy.Next, the head of the merged list.
The solution in Go
func mergeKLists(lists []*ListNode) *ListNode {
dummy := &ListNode{}
tail := dummy
for {
ind := -1
for i := 0; i < len(lists); i++ {
if lists[i] != nil && (ind == -1 || lists[i].Val < lists[ind].Val) {
ind = i
}
}
if ind == -1 {
break
}
tail.Next = &ListNode{Val: lists[ind].Val}
lists[ind] = lists[ind].Next
tail = tail.Next
}
return dummy.Next
}
The Go details worth a closer look:
- Pointers and
nil.*ListNodeis a pointer to a node, andnilmeans no node. The conditionlists[i] != nil && ...relies on short-circuit evaluation: when the first part is false,lists[i].Valis never evaluated, so no nil pointer is dereferenced. Reading a field through a nil pointer would panic. &ListNode{...}allocates. The composite literal with&creates a new node and gives back a pointer to it. Go decides where to store it and cleans it up automatically. There is nodeleteand no manual memory handling.- Fields left out are zero.
&ListNode{}hasVal0 andNextnil. The new nodes get aNextofniluntil the next pass links them. - The input nodes are copied, not relinked. The code creates a new node for each value and never changes an existing node's
Next, so the original lists stay intact. The entries of thelistsslice, however, are overwritten as they advance. When the function returns, every entry isnil. A caller that needs the slice afterwards should pass a copy. - Ties. The comparison is strictly
<, so with equal heads the list with the smaller position wins. The merged values are the same either way.
Complexity
Time: O(n * k). The outer loop makes n + 1 passes: one for each output node and a last one that finds nothing and breaks. Every pass scans k heads. The other work in a pass is a constant amount.
Space: O(1) beyond the answer. The merged list is built from one new node per value, as the output requires. Apart from that, the function keeps the dummy node and a few variables. Advancing lists[ind] reuses the input slice and allocates nothing.
Edge cases and common mistakes
- Empty slice. The scan loop doesn't run,
indstays -1, the function breaks at once and returnsdummy.Next, which isnil. - Only empty lists.
[nil]givesnilin the same way. - Some empty lists among others. They are skipped by the
!= niltest and never chosen. - Returning
dummy. That would put a fake leading node in front of the answer. Returndummy.Next. - Dereferencing before the nil test. Writing the comparison as
lists[i].Val < lists[ind].Valbefore checkinglists[i] != nilpanics on an exhausted list. Likewiselists[ind]is only read whenindis not -1, which the order of the conditions guarantees. - Forgetting to move
tail. Each new node would be attached to the same place, overwriting the previous one, and the merged list would hold a single value.
Testing
[[1,4,5],[1,3,4],[2,6]]gives[1,1,2,3,4,4,5,6].[]gives[].[[]]gives[].[[],[5],[]]gives[5].
