Remove Duplicates from Sorted List: Keep One of Each
Remove Duplicates from Sorted List is LeetCode problem 83. You are given the head of a sorted linked list. Delete all duplicate nodes so that each value appears only once. Return the modified list, still sorted.
Example: 1 -> 1 -> 2 -> null becomes 1 -> 2 -> null. Another: 1 -> 1 -> 2 -> 3 -> 3 -> null becomes 1 -> 2 -> 3 -> null.
Because the list is already sorted, all duplicates are adjacent. That one fact makes this problem much simpler than deduplicating an unsorted collection. You do not need a hash set. You do not need to sort anything. You just need to walk forward and skip nodes that repeat the current value.
The approach
Use a single pointer called curr that starts at the head. At each step, compare curr.val with curr.next.val:
- If they match, the next node is a duplicate. Skip it by setting
curr.next = curr.next.next. Do not advancecurryet, because the newcurr.nextmight also be a duplicate. - If they differ, the next node is a new value. Advance
currtocurr.next.
Keep going until curr.next is null. Then return the head.
That is the entire algorithm. One pointer, one comparison per iteration, no extra memory.
Visual walkthrough
Trace through the list 1 -> 1 -> 2 -> 3 -> 3 -> null. Watch the curr pointer (amber) as it moves through the list, skipping duplicate nodes (red) along the way.
Start: curr = node 1
curr starts at the head. curr.val is 1, and curr.next.val is also 1. Duplicate found.
Step 1: curr.val == curr.next.val (1 == 1). Skip the duplicate.
Set curr.next = curr.next.next, bypassing the duplicate. curr stays at node 1.
Step 2: curr.val != curr.next.val (1 != 2). Move curr forward.
No duplicate here, so advance curr to node 2. Node 1 is finalized.
Step 3: curr.val != curr.next.val (2 != 3). Move curr forward.
Values differ, so advance curr to node 3. Node 2 is finalized.
Step 4: curr.val == curr.next.val (3 == 3). Skip the duplicate.
Set curr.next = curr.next.next (null). The duplicate 3 is removed. curr.next is now null.
Done: curr.next is null. Loop ends.
All duplicates removed. Return head. The list is now 1 -> 2 -> 3 -> null.
Notice that curr only advances when the next value is different. When a duplicate is found, curr stays put and the duplicate gets bypassed. This ensures you catch consecutive runs of the same value, no matter how long they are.
Python solution
def deleteDuplicates(head):
curr = head
while curr and curr.next:
if curr.val == curr.next.val:
curr.next = curr.next.next
else:
curr = curr.next
return head
The logic mirrors the walkthrough exactly. When curr.val matches curr.next.val, you splice out the duplicate by rewiring the pointer. Otherwise, you move forward. The loop exits when curr is null or curr.next is null, meaning you have reached the end.
Complexity analysis
Time: O(n). You visit each node at most once. Even when you skip a duplicate, that node is never visited again. The total number of pointer operations across all iterations is at most n.
Space: O(1). You only use one pointer variable. The deduplication happens in-place by rewiring existing nodes. No extra data structures are needed.
The building blocks
This problem is built from one core building block:
In-place linked list deduplication. You walk a sorted list with a single pointer, comparing each node to its neighbor. When they match, you splice out the duplicate by skipping the next pointer over it. When they differ, you advance.
This technique generalizes naturally:
- Remove Duplicates from Sorted List II (LeetCode 82) removes all nodes that have duplicates, keeping only values that appear exactly once. Same traversal pattern, but you need a
prevpointer to handle removing the first occurrence too. - Remove Duplicates from Sorted Array (LeetCode 26) applies the same idea to arrays with a read/write pointer approach instead of pointer rewiring.
- Any problem that asks you to "remove" elements from a linked list in-place uses the same
curr.next = curr.next.nextsplice operation.
Once this pattern is muscle memory, any linked list removal problem becomes a variation rather than a new puzzle.
Edge cases
- Empty list (
headis null). The while condition fails immediately. Return null. - Single node.
curr.nextis null from the start. The loop never runs. Return the single node unchanged. - All nodes have the same value (e.g.,
1 -> 1 -> 1 -> 1). Each iteration finds a duplicate and splices it out.currnever advances. You end up with a single node. - No duplicates at all (e.g.,
1 -> 2 -> 3 -> 4). Each iteration finds different values and advancescurr. No splicing happens. The list is returned as-is.
A common mistake is advancing curr after removing a duplicate. If you always do curr = curr.next, you will skip the comparison between the current node and its new neighbor, potentially missing consecutive duplicates like 1 -> 1 -> 1.
From understanding to recall
This is a five-line solution. You will read it once and think you have it memorized. But the subtlety is in knowing when to advance curr and when to stay put. Under interview pressure, that if/else distinction is exactly the kind of detail that slips away.
The fix is not to read the solution more carefully. It is to type it from scratch, repeatedly, until the branching logic is automatic. Spaced repetition turns a solution you understand into a solution you can reproduce without hesitation.
Related posts
- Reverse Linked List - Another fundamental linked list operation that tests whether you truly understand pointer manipulation
- Linked List Cycle - A different single-pass linked list traversal where the key insight is how you move your pointers