83. Remove Duplicates from Sorted List
Problem Description
You are given the head
of a sorted linked list. Your task is to remove all duplicate values from the list so that each element appears only once. The linked list should remain sorted after removing duplicates.
The input is a linked list that is already sorted in ascending order. You need to traverse through the list and whenever you find consecutive nodes with the same value, you should keep only one occurrence and remove the others.
For example:
- If the input list is
1 -> 1 -> 2
, the output should be1 -> 2
- If the input list is
1 -> 1 -> 2 -> 3 -> 3
, the output should be1 -> 2 -> 3
The solution uses a single pointer cur
to traverse the linked list. At each step, it checks if the current node's value equals the next node's value. If they are equal, it skips the next node by updating the next
pointer to cur.next.next
. If they are different, it simply moves to the next node. This approach efficiently removes duplicates in a single pass through the list with O(n)
time complexity and O(1)
space complexity.
Intuition
Since the linked list is already sorted, all duplicate values will appear consecutively. This is the key insight that makes the problem straightforward to solve.
Think about walking through a sorted list like 1 -> 1 -> 2 -> 3 -> 3
. When we're at the first 1
, we can immediately see that the next node also contains 1
. Since we want to keep only one occurrence, we can simply skip over the duplicate by making our current node point directly to the node after the duplicate.
The natural approach is to use a single pointer to traverse the list. At each node, we ask: "Is my next neighbor the same as me?" If yes, we bypass that neighbor by connecting directly to the neighbor's next node (cur.next = cur.next.next
). If no, we know we've found a unique value and can safely move forward.
This works because:
- The sorted nature guarantees all duplicates are adjacent
- We only need to look one step ahead to detect duplicates
- By modifying the
next
pointers as we go, we can remove duplicates in-place without needing extra space
The beauty of this solution lies in its simplicity - we're essentially "short-circuiting" the list whenever we spot a duplicate, creating a direct path that skips over repeated values. We continue this process until we've examined every node, resulting in a clean list with no duplicates.
Learn more about Linked List patterns.
Solution Approach
We implement a single-pass traversal using a pointer cur
that starts at the head of the linked list. The algorithm works as follows:
-
Initialize the traversal: Set
cur = head
to start from the beginning of the linked list. -
Iterate through the list: Use a
while
loop that continues as long as bothcur
andcur.next
exist. We need both conditions because:- We need
cur
to be valid to access its value - We need
cur.next
to be valid to compare with the current node
- We need
-
Check for duplicates: At each node, compare
cur.val
withcur.next.val
:-
If they are equal (
cur.val == cur.next.val
): We've found a duplicate. Skip the next node by settingcur.next = cur.next.next
. This effectively removes the duplicate node from the list. Note that we don't movecur
forward yet because there might be more duplicates of the same value. -
If they are different: No duplicate found at this position. Move the pointer forward by setting
cur = cur.next
to continue checking the rest of the list.
-
-
Return the result: After the loop completes, return
head
which still points to the beginning of the now-deduplicated list.
The key insight is that when we find a duplicate, we stay at the current node and keep removing subsequent duplicates until we find a different value. Only then do we move forward. This ensures we handle cases where there are multiple consecutive duplicates (like 1 -> 1 -> 1 -> 2
).
Time Complexity: O(n)
where n
is the number of nodes, as we visit each node exactly once.
Space Complexity: O(1)
as we only use a single pointer variable and modify the list in-place.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the linked list: 1 -> 1 -> 2 -> 3 -> 3
Initial State:
head -> [1] -> [1] -> [2] -> [3] -> [3] -> null ^ cur
Step 1:
cur
points to first node (value = 1)- Compare
cur.val
(1) withcur.next.val
(1) - They're equal, so skip the duplicate:
cur.next = cur.next.next
head -> [1] -> [2] -> [3] -> [3] -> null ^ cur
Step 2:
cur
still points to first node (value = 1)- Compare
cur.val
(1) withcur.next.val
(2) - They're different, so move forward:
cur = cur.next
head -> [1] -> [2] -> [3] -> [3] -> null ^ cur
Step 3:
cur
points to node with value 2- Compare
cur.val
(2) withcur.next.val
(3) - They're different, so move forward:
cur = cur.next
head -> [1] -> [2] -> [3] -> [3] -> null ^ cur
Step 4:
cur
points to first node with value 3- Compare
cur.val
(3) withcur.next.val
(3) - They're equal, so skip the duplicate:
cur.next = cur.next.next
head -> [1] -> [2] -> [3] -> null ^ cur
Step 5:
cur
points to node with value 3cur.next
is null, so the loop terminates
Final Result: 1 -> 2 -> 3
The algorithm successfully removed all duplicates while maintaining the sorted order. Notice how when we found duplicates (steps 1 and 4), we stayed at the current node and modified the links, but when values were different (steps 2 and 3), we moved the pointer forward.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7from typing import Optional
8
9class Solution:
10 def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
11 """
12 Remove duplicates from a sorted linked list.
13
14 Args:
15 head: The head node of a sorted linked list
16
17 Returns:
18 The head of the linked list with duplicates removed
19 """
20 # Initialize current pointer to traverse the list
21 current = head
22
23 # Traverse the list until we reach the end
24 while current and current.next:
25 # Check if current node's value equals the next node's value
26 if current.val == current.next.val:
27 # Skip the duplicate node by updating the pointer
28 current.next = current.next.next
29 else:
30 # Move to the next node if no duplicate found
31 current = current.next
32
33 # Return the modified list
34 return head
35
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 /**
13 * Removes duplicates from a sorted linked list.
14 * Each element will appear only once in the resulting list.
15 *
16 * @param head The head of the sorted linked list
17 * @return The head of the linked list after removing duplicates
18 */
19 public ListNode deleteDuplicates(ListNode head) {
20 // Initialize current pointer to traverse the list
21 ListNode current = head;
22
23 // Traverse the list until we reach the end
24 while (current != null && current.next != null) {
25 // Check if current node's value equals the next node's value
26 if (current.val == current.next.val) {
27 // Skip the duplicate node by updating the pointer
28 // This effectively removes the duplicate from the list
29 current.next = current.next.next;
30 } else {
31 // No duplicate found, move to the next node
32 current = current.next;
33 }
34 }
35
36 // Return the modified list with duplicates removed
37 return head;
38 }
39}
40
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 /**
14 * Remove duplicates from a sorted linked list
15 * @param head: pointer to the head of the sorted linked list
16 * @return: pointer to the head of the list with duplicates removed
17 */
18 ListNode* deleteDuplicates(ListNode* head) {
19 // Initialize current pointer to traverse the list
20 ListNode* current = head;
21
22 // Traverse the list until we reach the end
23 while (current != nullptr && current->next != nullptr) {
24 // Check if current node value equals next node value
25 if (current->val == current->next->val) {
26 // Skip the duplicate node by updating the next pointer
27 // This effectively removes the duplicate from the list
28 current->next = current->next->next;
29 } else {
30 // Move to the next node if no duplicate found
31 current = current->next;
32 }
33 }
34
35 // Return the modified list
36 return head;
37 }
38};
39
1/**
2 * Definition for singly-linked list node
3 */
4interface ListNode {
5 val: number;
6 next: ListNode | null;
7}
8
9/**
10 * Removes duplicate values from a sorted linked list
11 * @param head - The head of the sorted linked list
12 * @returns The head of the linked list with duplicates removed
13 */
14const deleteDuplicates = function(head: ListNode | null): ListNode | null {
15 // Initialize current pointer to traverse the list
16 let current: ListNode | null = head;
17
18 // Traverse the list until we reach the end
19 while (current !== null && current.next !== null) {
20 // Check if current node's value equals the next node's value
21 if (current.next.val === current.val) {
22 // Skip the duplicate node by updating the pointer
23 current.next = current.next.next;
24 } else {
25 // Move to the next node if no duplicate found
26 current = current.next;
27 }
28 }
29
30 // Return the modified list
31 return head;
32};
33
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the linked list. The algorithm traverses the linked list exactly once using a single while loop. In each iteration, it either removes a duplicate node (by adjusting the next
pointer) or moves to the next node. Even when removing duplicates, each node is visited at most once, resulting in linear time complexity.
Space Complexity: O(1)
. The algorithm uses only a constant amount of extra space. It modifies the linked list in-place by adjusting pointers and only uses one additional variable cur
to track the current position in the list. No additional data structures are created that scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Moving the pointer too early when handling multiple consecutive duplicates
The Problem:
A common mistake is to always move the current
pointer forward after removing a duplicate, like this:
while current and current.next:
if current.val == current.next.val:
current.next = current.next.next
current = current.next # WRONG: Moving forward too early!
else:
current = current.next
This fails when there are more than two consecutive duplicates. For example, with the list 1 -> 1 -> 1 -> 2
, after removing the first duplicate, we'd have 1 -> 1 -> 2
. But if we move current
forward, we'd skip checking the new current.next
, leaving a duplicate in the final result.
The Solution:
Keep the current
pointer at the same position after removing a duplicate. Only move forward when the current value differs from the next value:
while current and current.next:
if current.val == current.next.val:
current.next = current.next.next
# Don't move current here - stay to check for more duplicates
else:
current = current.next # Only move when values are different
Pitfall 2: Not checking for null pointers properly
The Problem:
Checking only current
without checking current.next
can lead to a NullPointerException:
while current: # Missing current.next check
if current.val == current.next.val: # current.next might be None!
current.next = current.next.next
The Solution:
Always check both current
and current.next
in the loop condition:
while current and current.next: # Both checks are necessary
if current.val == current.next.val:
current.next = current.next.next
Pitfall 3: Forgetting to handle edge cases
The Problem: Not considering special cases like:
- Empty list (
head = None
) - Single node list
- List with all identical values
The Solution: The algorithm naturally handles these cases:
- Empty list: The while loop never executes, returns
None
- Single node:
current.next
isNone
, loop never executes, returns the single node - All identical: The algorithm keeps the first occurrence and removes all others
The beauty of the solution is that these edge cases don't require special handling - the main logic covers them all.
In a binary min heap, the minimum element can be found in:
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!