Remove Duplicates from Sorted List
Given the head of a sorted (ascending) linked list, delete nodes so that each distinct value appears exactly once, keeping one copy of each. The result stays sorted. Modify the list in place and return its head — the head value is the smallest, so it is never the copy that gets removed, and the head pointer is unchanged. An empty list returns an empty list.
- The number of nodes in the list is in the range [0, 300]
- -100 <= Node.val <= 100
- The list is guaranteed to be sorted in ascending order.
head = [1,1,2][1,2]head = [1,1,2,3,3][1,2,3]head = [1,1,1][1]We are handed a linked list whose values are already sorted, and we must throw away repeats so each value survives exactly once — [1, 1, 2] becomes [1, 2]. We only walk forward (the traversal from the previous problem), and we want to do it in place, without building a second list.
If the list were in any order, duplicates could be anywhere — a second 1 might sit ten nodes away from the first. To catch that, you would carry a set of values already seen: at each node, ask "have I seen this value before?"; if yes, splice the node out; if no, record it. A set answers that question in roughly constant time, so this runs in O(N) time — but it spends O(N) extra memory on the set, up to one entry per distinct value.
seen = set()
prev, curr = None, head
while curr:
if curr.val in seen:
prev.next = curr.next # splice out the repeat
else:
seen.add(curr.val)
prev = curr
curr = curr.nextNow look hard at what that set is buying us. It exists to answer "have I seen this value?" for values that might appear far apart. But the problem swore the list is sorted. In a sorted list, equal values cannot be far apart — they are physically forced to sit in one unbroken run, immediate neighbors of each other. (If two 1s had anything other than a 1 between them, that middle value would be both ≥ 1 and ≤ 1, i.e. also a 1 — so there is no gap.) The set is remembering, at O(N) cost, a fact the ordering already guarantees for free. That is the waste.
Because every group of equal values is one contiguous run, you never have to remember the past. Standing on any node, the only place a duplicate of its value can hide is directly after it. So the entire memory of "what have I seen" collapses to a single comparison: is my current value equal to my neighbor's? That comparison needs no set, no extra space — just the two nodes already in front of you.
Keep one pointer curr. Compare curr.val to curr.next.val. If they are equal, the neighbor is a repeat, so bypass it: point curr.next to curr.next.next, unlinking the duplicate. If they differ, the neighbor is a genuinely new value, so advance curr onto it.
curr = head
while curr and curr.next:
if curr.val == curr.next.val:
curr.next = curr.next.next # drop the duplicate; DO NOT advance
else:
curr = curr.next # new value; safe to step forward
return headcurr — we stay put and compare against the new neighbor. This is the classic trap. Consider [1, 1, 1]: standing on the first 1 we drop the second, leaving 1 → 1. If we had stepped forward now, curr would land on the third 1 with nothing before it to compare against, and that third 1 would survive as a phantom duplicate. Staying put means we re-examine the same node until its neighbor is finally different, so a run of any length collapses to one. We advance only on a mismatch — the one moment we are certain the current value is done. The loop guard curr and curr.next matters for the same reason: the comparison reads curr.next.val, so we must confirm a neighbor exists before touching it, which also makes the empty list and single-node list exit immediately.curr on the first 1. Neighbor is 1 — equal. Drop it: list becomes 1 → 2 → 3 → 3. Stay on the first 1.curr still on 1. New neighbor is 2 — different. Advance to 2.curr on 2. Neighbor is 3 — different. Advance to 3.curr on the first 3. Neighbor is 3 — equal. Drop it: list becomes 1 → 2 → 3. Stay on 3.curr on 3. curr.next is now null — the guard fails. Stop. Result: [1, 2, 3].Notice the two duplicate groups were handled with zero memory, and the run of two 3s at the very end still terminated cleanly because the guard checks for a neighbor first.
Same single pass — O(N) time — but the set is gone, so space drops from O(N) to O(1). The whole saving came from reading the precondition: "sorted" quietly promised that equal things are adjacent, which turned a remember-everything problem into a compare-your-neighbor one. Train that reflex: when a problem hands you a sorted (or otherwise ordered) input, ask what that ordering makes adjacent, because adjacency is what lets you replace memory with a local comparison. And notice the exact contract here — we keep one copy of each value. The sibling problem that deletes every node that ever had a duplicate (leaving [1, 2, 3, 3] as just [1, 2]) needs a dummy node before the head and a slightly different guard; that variation is Remove Duplicates from Sorted List II.
Pointer Reassignment
next.val == curr.val