You are given the head of an unsorted singly linked list. Your task is to remove all duplicate nodes from the list so that each value appears only once. The order of the nodes should remain the same as their first occurrence.
Constraints:
1 -> 2 -> 3 -> 2 -> 4 -> 3
1 -> 2 -> 3 -> 4
When approaching this problem, the first idea that comes to mind is to compare each node with every other node to find duplicates. However, this brute-force approach is inefficient, especially for long lists.
To optimize, we can use extra memory to keep track of values we've already seen. This way, as we traverse the list, we can quickly check if a value has already appeared and remove the node if it has. This is similar to how you might keep a checklist to avoid repeating items when packing for a trip.
The key is to balance between time efficiency and the amount of extra space used.
To efficiently remove duplicates from an unsorted linked list, we can use a hash set to record which values we've already encountered. Here's how the algorithm works step-by-step:
HashSet
or Set
) to store the values we've seen so far.
prev
: Points to the previous node (or None
at the start).curr
: Points to the current node being examined.curr.val
is not in the set, add it and move both pointers forward.curr.val
is already in the set, remove curr
by setting prev.next
to curr.next
, then move curr
forward (but prev
stays at the last valid node).curr
is None
.
This approach uses a set for O(1) lookups, making the overall process efficient.
Let's walk through the input 1 -> 2 -> 3 -> 2 -> 4 -> 3
step by step:
1 -> 2 -> 3 -> 2 -> 4 -> 3
1 -> 2 -> 3 -> 4 -> 3
1 -> 2 -> 3 -> 4
1 -> 2 -> 3 -> 4
Brute-force approach:
For each node, check every other node for duplicates. This leads to O(n2) time complexity and O(1) extra space.
Optimized approach (using a set):
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def removeDuplicates(head):
seen = set()
curr = head
prev = None
while curr:
if curr.val in seen:
prev.next = curr.next
else:
seen.add(curr.val)
prev = curr
curr = curr.next
return head
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
#include <unordered_set>
ListNode* removeDuplicates(ListNode* head) {
std::unordered_set<int> seen;
ListNode* curr = head;
ListNode* prev = nullptr;
while (curr) {
if (seen.count(curr->val)) {
prev->next = curr->next;
} else {
seen.insert(curr->val);
prev = curr;
}
curr = curr->next;
}
return head;
}
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
import java.util.HashSet;
class Solution {
public ListNode removeDuplicates(ListNode head) {
HashSet<Integer> seen = new HashSet<>();
ListNode curr = head;
ListNode prev = null;
while (curr != null) {
if (seen.contains(curr.val)) {
prev.next = curr.next;
} else {
seen.add(curr.val);
prev = curr;
}
curr = curr.next;
}
return head;
}
}
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
var removeDuplicates = function(head) {
let seen = new Set();
let curr = head;
let prev = null;
while (curr) {
if (seen.has(curr.val)) {
prev.next = curr.next;
} else {
seen.add(curr.val);
prev = curr;
}
curr = curr.next;
}
return head;
};
In summary, removing duplicates from an unsorted linked list can be done efficiently by using a hash set to keep track of seen values. This approach allows us to process each node in a single pass, removing duplicates in-place and preserving the order of first occurrences. The main insight is to trade a small amount of extra memory for a significant speedup, making this solution both practical and elegant for real-world data.