A detailed guide to solving Remove Duplicates from Sorted List II with a dummy node and pointer rewiring.
Problem Restatement
We are given the head of a sorted linked list.
We need to delete every node whose value appears more than once.
This is stronger than keeping one copy.
If a value appears two or more times, all nodes with that value must be removed.
For example:
1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5The values 3 and 4 are duplicated, so all 3 nodes and all 4 nodes are removed.
The result is:
1 -> 2 -> 5The official statement says to delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list. The returned list should remain sorted. Constraints include 0 <= number of nodes <= 300, -100 <= Node.val <= 100, and the list is sorted in ascending order.
Input and Output
| Item | Meaning |
|---|---|
| Input | Head of a sorted linked list |
| Output | Head of the modified linked list |
| Remove rule | Remove every value that appears more than once |
| Keep rule | Keep only values that appear exactly once |
| Order | The result remains sorted |
Function shape:
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
...Examples
Example 1:
head = [1, 2, 3, 3, 4, 4, 5]The values 3 and 4 appear more than once.
Remove all nodes with those values.
Answer:
[1, 2, 5]Example 2:
head = [1, 1, 1, 2, 3]The value 1 appears three times.
Remove all 1 nodes.
Answer:
[2, 3]Example 3:
head = [1, 1]The value 1 appears more than once.
Answer:
[]First Thought: Count Frequencies
One simple way is to make two passes.
First pass:
count[value] += 1Second pass:
keep only nodes where count[value] == 1This works, but it uses extra space.
Because the linked list is sorted, duplicate values appear in consecutive groups. We can remove duplicate groups in one pass with constant extra space.
Key Insight
Since the list is sorted, equal values are adjacent.
So the list can be viewed as groups:
1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5Groups:
[1], [2], [3, 3], [4, 4], [5]We keep groups of size 1.
We remove groups of size greater than 1.
The hard part is deleting duplicates at the head.
For example:
1 -> 1 -> 1 -> 2 -> 3The real head must change from 1 to 2.
A dummy node solves this cleanly.
dummy -> 1 -> 1 -> 1 -> 2 -> 3The dummy node gives us a stable node before the real head, so we can rewire dummy.next if the first group must be removed.
Algorithm
Create a dummy node before head.
Use two pointers:
| Pointer | Meaning |
|---|---|
prev | Last node before the current group |
cur | Current node being inspected |
Start:
dummy.next = head
prev = dummy
cur = headThen scan the list.
For each group:
- Check whether
curhas the same value ascur.next. - If yes, move
curuntil the last node of that duplicate group. - Remove the whole group by setting:
prev.next = cur.next- If no duplicate was found, move
prevforward:
prev = prev.next- Move
curforward.
At the end, return:
dummy.nextWalkthrough
Use:
1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5Add dummy:
dummy -> 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5Start:
prev = dummy
cur = 1Value 1 has no duplicate.
Move prev to 1.
dummy -> 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
prev
cur = 2Value 2 has no duplicate.
Move prev to 2.
dummy -> 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
prev
cur = 3Value 3 has a duplicate.
Move cur to the last 3.
Then remove the whole group:
prev.next = cur.nextNow the list becomes:
dummy -> 1 -> 2 -> 4 -> 4 -> 5prev stays at 2.
Now cur moves to 4.
Value 4 has a duplicate.
Skip all 4 nodes and rewire:
prev.next = cur.nextNow the list becomes:
dummy -> 1 -> 2 -> 5Value 5 has no duplicate, so it stays.
Return:
dummy.nextwhich is:
1 -> 2 -> 5Correctness
The list is sorted, so all nodes with the same value appear in one consecutive group.
The algorithm processes one such group at a time.
If the current group has size 1, then its value appears exactly once in the original list. The algorithm keeps it by moving prev forward to that node.
If the current group has size greater than 1, then its value is duplicated. The algorithm moves cur to the last node of that group, then connects prev.next to the node after the group. This removes every node with that duplicated value.
The dummy node ensures the same logic works when the first group is removed. In that case, prev is the dummy node, and rewiring prev.next changes the returned head.
Because every group is either kept exactly once or removed completely, and because all groups are processed in order, the final list contains exactly the values that appeared once in the original sorted list.
Therefore, the algorithm is correct.
Complexity
Let:
n = number of nodes| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each node is visited at most a constant number of times |
| Space | O(1) | Only a few pointers are used |
Implementation
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(0, head)
prev = dummy
cur = head
while cur:
has_duplicate = False
while cur.next and cur.val == cur.next.val:
has_duplicate = True
cur = cur.next
if has_duplicate:
prev.next = cur.next
else:
prev = prev.next
cur = cur.next
return dummy.nextCode Explanation
Create a dummy node before the list:
dummy = ListNode(0, head)Set prev to the node before the current group:
prev = dummySet cur to the current group start:
cur = headFor every group, we first assume no duplicate has been found:
has_duplicate = FalseThen we walk through all nodes with the same value:
while cur.next and cur.val == cur.next.val:
has_duplicate = True
cur = cur.nextIf a duplicate group was found, remove the whole group:
if has_duplicate:
prev.next = cur.nextprev does not move, because the next group may also need to be removed.
If the group was unique, keep it and advance prev:
else:
prev = prev.nextThen move to the next group:
cur = cur.nextFinally, return the real head after all rewiring:
return dummy.nextTesting
The helpers below make local testing easier.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def build_list(values):
dummy = ListNode()
cur = dummy
for value in values:
cur.next = ListNode(value)
cur = cur.next
return dummy.next
def to_list(head):
values = []
while head:
values.append(head.val)
head = head.next
return values
def check(values, expected):
s = Solution()
head = build_list(values)
result = s.deleteDuplicates(head)
assert to_list(result) == expected
def run_tests():
check([1, 2, 3, 3, 4, 4, 5], [1, 2, 5])
check([1, 1, 1, 2, 3], [2, 3])
check([1, 1], [])
check([1], [1])
check([], [])
check([1, 2, 2], [1])
check([1, 1, 2], [2])
check([1, 2, 3], [1, 2, 3])
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[1, 2, 3, 3, 4, 4, 5] | Main example |
[1, 1, 1, 2, 3] | Duplicates at the head |
[1, 1] | Entire list removed |
[1] | Single unique node |
[] | Empty list |
[1, 2, 2] | Duplicates at the tail |
[1, 1, 2] | Head changes |
[1, 2, 3] | No duplicates |