Merge sorted linked lists
1. Problem statement
Merge two sorted linked lists together into a new linked list (Aziz et al., 2018, p. 92).
2. Insights
Because the input lists are already sorted, we only need to traverse through them once. We just have to make sure that we choose the node that has the smallest value in either list, and then proceed to the next node.
The other thing to keep in mind is that we want to append to the list by adding to a tail node. Otherwise if we keep appending from the head, we'll end up prepending things instead of appending things, ending up with nodes in reversed order.
Note that we use the linked list library implemented in "Linked
lists". There, the insert()
method creates a new LinkedList
object each
time. This is a bit expensive because we already have the objects allocated in
the input lists a
and b
.
3. Solution(s)
3.1. Always append to the tail
Here's the code.
Now let's go over this in detail.
First we create a new linked list node. We create two references to it, head
and tail
. We will modify tail by moving it along down the linked list we will
build up when we traverse through the input lists. When we're done we will
return the head
node which we will leave untouched during this algorithm.
We assume that the input lists' head nodes do not contain any data themselves.
And so we "shift" the head nodes of a
and b
by one node as a preparatory
step.
Now comes the traversal. We traverse as long as either input list has nodes.
Within each iteration, we check for 2 main cases:
- both
a
andb
have nodes, or - only one or both are at the tail (
None
type).
If both lists have nodes, we do the comparison check to determine which node has
the smaller element. Then we make tail.next
point to this node. We then
advance the node we chose to its next one (because we must not check this node
again in a future comparison).
If either or both input lists are empty, then we simply make tail.next
point
to the non-empty one. If both are empty then a or b
evaluates to None
, which
is still what we want. Then we break out of the loop to save time because there
is nothing more to compare.
Finally before we end the iteration, we advance the tail node. This is important because we want to append to the list as we traverse along the input lists to find the next smallest element.
When we're done we just need to return the original head node.
3.1.1. Complexity
- Time: \(O(a+b)\) where \(a\) and \(b\) are the number of nodes in the input lists for the worst-case, where both input lists have similar numbers of nodes. In the best case, one list is much shorter than the other and we can break out of the loop early.
- Space: \(O(1)\) because we only create 1 new node for the returned
head
node.