p07 Merge Two Sorted Lists — Progressive Hints
Rule: Read ONE hint. Set a 5-minute timer. Try again. Only read the next hint if the timer expires.
Hint 1 — Pattern family
The first node of your output isn’t special — except your code probably treats it that way. There’s a single allocation trick that makes “first append” identical to every other append. Reach for it.
Hint 2 — Data structure / state
You walk both lists simultaneously, always advancing the one whose current value is smaller. You need a way to append to your output without re-finding its end every time. So: keep a pointer to the output’s tail.
Hint 3 — The key insight
Use a dummy (sentinel) node before the real output head. Then tail = dummy initially. Every append is tail.next = chosen; tail = tail.next. No special case for the first node. Return dummy.next (skip the dummy).
When one list is exhausted, the other’s remainder is already sorted. Just point at it. Don’t iterate.
Hint 4 — Pseudocode skeleton
dummy = new ListNode()
tail = dummy
while l1 and l2:
if l1.val <= l2.val: # <= for stability
tail.next = l1
l1 = l1.next
else:
tail.next = l2
l2 = l2.next
tail = tail.next
tail.next = l1 if l1 else l2 # leftover tail — O(1) attach
return dummy.next
Two things to check yourself: (a) you wrote <=, not <; (b) the leftover attach is ONE line, not a second while loop.
Hint 5 — Full algorithm
The iterative version is as in Hint 4. Loop invariant: at the top of each iteration, dummy.next ... tail is a sorted list of all consumed nodes, and the remaining l1 and l2 are sorted suffixes whose first elements are ≥ tail.val. When one becomes None, the other’s entire remainder is sorted and ≥ tail, so attaching it preserves the sorted property.
Why dummy node: without it, you’d write if result is None: result = chosen; tail = chosen for the first append, creating a branch in your hot loop. With it, every iteration is identical. This is the “sentinel removes the special case” pattern — it reappears in every list mutation problem.
Recursive variant (Google likes this):
def merge(l1, l2):
if l1 is None: return l2
if l2 is None: return l1
if l1.val <= l2.val:
l1.next = merge(l1.next, l2); return l1
l2.next = merge(l1, l2.next); return l2
Elegant but O(N1+N2) stack — risky in Python past N=1000.
Time O(N1+N2). Space O(1) iterative, O(N1+N2) recursive.