Problem overview
The method mergeKLists receives an array of k linked lists, each sorted in ascending order, as [ListNode?]. Each element is the head node of a list, or nil for an empty list. The method merges them all into one sorted linked list and returns its head, or nil when there is nothing to merge.
For the lists 1->4->5, 1->3->4 and 2->6, the merged list is 1->1->2->3->4->4->5->6. For an empty array, and for an array holding one empty list, the result is an empty list.
This lesson asks for O(n × k) time, where n is the total number of nodes and k is the number of lists. A faster O(n log k) approach with a min-heap is a separate lesson.
Approach: repeatedly take the smallest head
Because every input list is sorted, the smallest number not yet used is always at the front of one of the lists. So the merged list can be built one node at a time: look at the head of every list, pick the smallest, append its value to the output, and advance that list by one node. Repeat until every list is exhausted.
Finding the smallest head means scanning all k lists, which costs O(k). That scan happens once per output node, so with n nodes the total is O(n × k). It is easy to get right, and it is the baseline that the heap version improves.
Two details make the code short. The first is a dummy node placed in front of the output. Appending to the end of a list needs a special case when the list is empty, since there is no last node yet. With a dummy node there is always a last node, and the real head is dummy.next at the end. The second is a tail variable that always refers to the last node appended.
The loop needs to know when to stop, which is when every entry of the array is nil. A small helper, anyNonNull, answers that.
Step by step
- Create the dummy node with
let dummy = ListNode(). It is aletbecause the variable never points to a different node; the node'snextfield is what changes. - Keep a tail with
var tail = dummy. It starts at the dummy and moves to each node as it is appended. - Loop while any list has nodes:
while anyNonNull(lists). The helper is written last. - Prepare to pick a winner.
var ind = -1stores the index of the list with the smallest head. The value-1means that no list has been picked yet. - Scan every list with
for i in 0..<lists.count. This scan is theO(k)part. - Update the winner. List
ibecomes the new candidate when it is notniland either nothing has been picked yet or its head value is strictly smaller than the current best. The test on-1comes first so thatlists[ind]is never read with an invalid index. - Append a node with the winning value:
tail.next = ListNode(lists[ind]!.val). - Advance the winning list with
lists[ind] = lists[ind]!.next. Its next node, possiblynil, becomes its new head. - Move the tail onto the node just appended:
tail = tail.next!. - Return
dummy.nextafter the loop. That is the first real node, ornilif nothing was appended. - Write the helper
anyNonNull, which receives the array and returns aBool. - Loop over the entries with
for l in lists. - Return
trueat the first non-nilentry. One live list is enough to answer the question. - Return
falseafter the loop. Every entry wasnil.
The solution in Swift
class Solution {
func mergeKLists(_ lists: [ListNode?]) -> ListNode? {
var lists = lists
let dummy = ListNode()
var tail = dummy
while anyNonNull(lists) {
var ind = -1
for i in 0..<lists.count {
if lists[i] != nil && (ind == -1 || lists[i]!.val < lists[ind]!.val) {
ind = i
}
}
tail.next = ListNode(lists[ind]!.val)
lists[ind] = lists[ind]!.next
tail = tail.next!
}
return dummy.next
}
func anyNonNull(_ lists: [ListNode?]) -> Bool {
for l in lists {
if l != nil {
return true
}
}
return false
}
}
The Swift details worth a closer look:
var lists = lists. Function parameters are constants, but the loop needs to move each list's head forward. Declaring a localvarwith the same name shadows the parameter with a mutable copy. An array is a value type, so the caller's array is not affected. The nodes themselves are never modified either, only the references in the local array.- Optionals everywhere.
ListNode?is either a node ornil. Comparing withlists[i] != nilchecks for a node without unwrapping it. The exclamation mark inlists[i]!.valunwraps the optional and crashes if it isnil, so each!in the solution is guarded by an earlier check. - Short-circuit
&&and||. Inlists[i] != nil && (...), the right side runs only if the left side is true, solists[i]!is safe. Inind == -1 || lists[i]!.val < lists[ind]!.val, the right side runs only whenindis a valid index. - The result is a new list.
ListNode(lists[ind]!.val)copies the value into a fresh node, so the merged list shares no nodes with the inputs. After merging[1, 2]alone, the returned head is a different object from the input head, and the input list still reads[1, 2]. tail = tail.next!is safe because the line above has just settail.nextto a new node.- Strict
<keeps ties in input order. When two heads are equal, the earlier list keeps the win, so equal values leave the lists in the order the lists were given.
Complexity
Time: O(n × k). The outer loop runs once per output node, so n times. Each pass scans k heads and calls anyNonNull, which looks at up to k entries. That is O(k) per pass.
Space: O(k) beyond the returned list. The merged list needs one new node per value, as it must be returned. The extra bookkeeping is the dummy node, a few variables and the local copy of the array of heads, which holds k references.
Edge cases and common mistakes
- No lists.
[]makesanyNonNullfalse at once, and the method returnsdummy.next, which isnil. - Only empty lists.
[nil]behaves the same way and also returnsnil. - Empty lists mixed with others. For
[nil, 2, nil, -1->3]the scan skips thenilentries and the result is-1->2->3. The!= niltest in the scan is what makes that safe. - Negative values. Picking the first non-empty list as the starting winner, through
ind == -1, works for any value. Starting with a "smallest so far" of 0 would go wrong for negative inputs. - Reading
lists[ind]whileindis -1. Swift stops the program with an index-out-of-range error. Theind == -1 ||check prevents it. - Forgetting to advance the chosen list. The loop would pick the same node forever, because
anyNonNullnever becomes false. - Returning
dummy. The dummy node's value is a placeholder, and the answer starts atdummy.next.
Testing
[1->4->5, 1->3->4, 2->6]gives1->1->2->3->4->4->5->6.[]gives an empty list (nil).[nil]gives an empty list.[nil, 2, nil, -1->3]gives-1->2->3.
