Linked List · 6 of 36
Remove Duplicates in a sorted Linked List
In a chain of boxes already in order, copies always sit next to each other, so walk once and skip every box that matches the one before it.
- Simple way
- time O(n)
room O(n) - Clever way
- time O(n)
room O(1)
The problem, simply
You get a linked list whose numbers are already sorted from small to big, like 1 -> 1 -> 2 -> 3 -> 3. Some numbers appear more than once.
Remove the extra copies so every number appears only once, and keep the order. Give back the head. Change the arrows; do not build a new chain.
A story to remember it
Picture it
A sorted chain. The copies of 1 sit together, and the three 3s sit together. The red boxes are the extra copies to remove.
- rejected
Examples
A normal case
- In
1 -> 1 -> 2 -> 3 -> 3 -> 3 -> 4- Out
1 -> 2 -> 3 -> 4
One extra 1 and two extra 3s are removed.
All the same
- In
5 -> 5 -> 5 -> 5- Out
5
The finger never moves. It keeps peeling off its neighbor until there is none.
No copies
- In
1 -> 2 -> 3- Out
1 -> 2 -> 3
Every neighbor is different, so the finger walks to the end and nothing changes.
Copies at the end
- In
1 -> 2 -> 2- Out
1 -> 2
The last box gets skipped, so box 2 now points to null.
Empty chain
- In
null- Out
null
Nothing to clean. Do not crash on head.next.
Way 1: the simple way
Keep a bag of numbers you have already seen, a set. Walk the chain. If the box's number is in the bag, unhook the box. If not, put the number in the bag and move on.
previs the last box you kept.curris the box you are checking.- If
curr.valis in the bag, makeprev.nextskip overcurr. - Otherwise add it to the bag and move
prevtocurr.
This works even when the chain is not sorted, which is exactly why it wastes the hint. It needs a bag as big as the chain.
Halfway: the bag holds 1, 2 and 3. The next 3 is already in the bag, so it gets unhooked.
- checking
- done
- rejected
import java.util.*;
class Solution {
public ListNode deleteDuplicates(ListNode head) {
Set<Integer> seen = new HashSet<>();
ListNode prev = null;
for (ListNode curr = head; curr != null; curr = curr.next) {
if (seen.contains(curr.val)) {
prev.next = curr.next; // unhook the copy, prev stays
} else {
seen.add(curr.val); // first time we see this number
prev = curr;
}
}
return head;
}
}How long it takes: one walk, and each bag check is about one step, so O(n). Extra room: the bag can hold every number, O(n).
Way 2: the clever way
Because the chain is sorted, a copy is always the very next box. So you only compare each box with its neighbor.
- Put
curron the head. - While
currand its next box exist: if they hold the same number, setcurr.next = curr.next.nextto skip the copy, and stay put. Otherwise movecurrforward. - Give back the head.
Peel off the neighbor while it is a copy
01 / 11
curr starts on the head, which holds 1.
- looking now
- done
- answer
- rejected
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode curr = head;
while (curr != null && curr.next != null) {
if (curr.val == curr.next.val) {
curr.next = curr.next.next; // skip the copy, stay here
} else {
curr = curr.next; // different, move on
}
}
return head;
}
}How long it takes: every box is looked at once, either kept or skipped, O(n). Extra room: one finger, O(1).
Watch out for
- Check
curr != nullbeforecurr.next, so the empty chain is safe. - The head never changes in this problem, because the first box is always kept.
- In C or C++ you must free the skipped box. In Java the garbage collector cleans it up.
What interviewers ask next
- Remove every number that has copies, keeping only numbers that appear once (LeetCode 82). Now the head can disappear, so start with a dummy box before the head. For each run of equal numbers, if the run is longer than one, make the box before the run point past the whole run.
- What if the chain is not sorted? Copies are no longer neighbors. Use the bag of seen numbers (Way 1), or sort the chain first with merge sort.
- Can you do it with recursion? Clean the rest of the chain first, then if the head equals its new neighbor, return the neighbor. It uses
O(n)stack room. - Same idea on a sorted array (LeetCode 26)? Yes. A write finger and a read finger; write only when the number differs from the last written one.
Remember this
Grown-up words: the boxes are nodes, the bag of seen numbers is a hash set, skipping a box is changing its next pointer, and the fake front box is a dummy node.