103. Merge Two Sorted Linked Lists
Problem
Merge two nondecreasing, finite, node-disjoint singly linked lists by relinking their existing nodes. Return the sorted merged head. Equal values are taken from the first list before the second. Either head may be None.
Examples
Example 1
list_values(merge_lists(build_list([1,4]), build_list([2,3])))
=> [1, 2, 3, 4]Only the selected source advances.
Example 2
list_values(merge_lists(None, build_list([2])))
=> [2]The nonempty suffix becomes the result.
Example 3
merge_lists(None, None)
=> NoneNo real node is attached.
Reveal the worked solution and reasoning
Why this mechanism works
This is the two-frontier sorted merge with links replacing array indices. A dummy tail gives every appended node the same attachment operation. Once one list ends, its partner's suffix is already sorted and can be attached with one link assignment.
State and boundaries
a and b are unread heads. tail is the last node of the committed sorted chain beginning at dummy.next.
Protocol
- Compare unread heads while both exist.
- Link the smaller head after tail.
- Advance only that source.
- Advance tail.
- Attach the remaining suffix.
- Return dummy.next.
Visualization
a: 1 -> 4 -> None
b: 2 -> 3 -> None
merged: dummy -> 1 -> 2 -> 3 -> 4Python implementation
from structures import ListNode, build_list, list_values
def merge_lists(a, b):
dummy = ListNode()
tail = dummy
while a is not None and b is not None:
if a.val <= b.val:
tail.next, a = a, a.next
else:
tail.next, b = b, b.next
tail = tail.next
tail.next = a if a is not None else b
return dummy.next
Worked execution
list_values(merge_lists(build_list([1,4]), build_list([2,3])))
L4 merge_lists: dummy = ListNode() a=N1(val=1,next=N2,random=None); b=N3(val=2,next=N4,random=None); dummy=N5(val=0,next=None,random=None) L5 merge_lists: tail = dummy a=N1(val=1,next=N2,random=None); b=N3(val=2,next=N4,random=None); dummy=N5(val=0,next=None,random=None); tail=N5(val=0,next=None,random=None) L8 merge_lists: tail.next, a = a, a.next a=N2(val=4,next=None,random=None); b=N3(val=2,next=N4,random=None); dummy=N5(val=0,next=N1,random=None); tail=N5(val=0,next=N1,random=None) L11 merge_lists: tail = tail.next a=N2(val=4,next=None,random=None); b=N3(val=2,next=N4,random=None); dummy=N5(val=0,next=N1,random=None); tail=N1(val=1,next=N2,random=None) L10 merge_lists: tail.next, b = b, b.next a=N2(val=4,next=None,random=None); b=N4(val=3,next=None,random=None); dummy=N5(val=0,next=N1,random=None); tail=N1(val=1,next=N3,random=None) L11 merge_lists: tail = tail.next a=N2(val=4,next=None,random=None); b=N4(val=3,next=None,random=None); dummy=N5(val=0,next=N1,random=None); tail=N3(val=2,next=N4,random=None) L10 merge_lists: tail.next, b = b, b.next a=N2(val=4,next=None,random=None); b=None; dummy=N5(val=0,next=N1,random=None); tail=N3(val=2,next=N4,random=None) L11 merge_lists: tail = tail.next a=N2(val=4,next=None,random=None); b=None; dummy=N5(val=0,next=N1,random=None); tail=N4(val=3,next=None,random=None) L12 merge_lists: tail.next = a if a is not None else b a=N2(val=4,next=None,random=None); b=None; dummy=N5(val=0,next=N1,random=None); tail=N4(val=3,next=N2,random=None)
Result: [1, 2, 3, 4]. Only the selected source advances.
list_values(merge_lists(None, build_list([2])))
L4 merge_lists: dummy = ListNode() a=None; b=N1(val=2,next=None,random=None); dummy=N2(val=0,next=None,random=None) L5 merge_lists: tail = dummy a=None; b=N1(val=2,next=None,random=None); dummy=N2(val=0,next=None,random=None); tail=N2(val=0,next=None,random=None) L12 merge_lists: tail.next = a if a is not None else b a=None; b=N1(val=2,next=None,random=None); dummy=N2(val=0,next=N1,random=None); tail=N2(val=0,next=N1,random=None)
Result: [2]. The nonempty suffix becomes the result.
Why the solution is correct
The smaller head is no greater than any unread value in either sorted list. Attaching it preserves sorted order and consumes one node. The remaining suffix can be attached after exhaustion because all its values follow the last committed frontier choice.
Time and space
O(n+m) time and O(1) auxiliary space beyond one dummy node; inputs are structurally consumed.
Common mistakes
Advancing a source before saving or attaching its node can lose it. Shared input nodes violate the node-disjoint precondition and can create unintended cycles.
Reasoning card
State: a and b are unread heads. tail is the last node of the committed sorted chain beginning at dummy.next.
Invariant: Everything after dummy through tail is final and sorted.
Decision to rehearse: Use a dummy tail and consume the smaller current node.
Finish: Return dummy.next
Transfer exercise
Merge k sorted linked lists with one heap entry per source, retaining a source identifier for equal values.