Remove Linked List Elements
Problem Statement
Given the head of a linked list and an integer val, remove every node whose value equals val and return the new head of the list.
Input
The head pointer of a singly linked list and an integer val to remove.
Output
The head pointer of the list after all nodes with value val have been removed.
Constraints
- •
0 <= number of nodes <= 10000 - •
1 <= Node.val <= 50 - •
0 <= val <= 50
Examples
Example 1
head = **1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6 -> null**, val = 6
**1 -> 2 -> 3 -> 4 -> 5 -> null**Example 2
head = **7 -> 7 -> 7 -> 7 -> null**, val = 7
**null**Example 3
head = **1 -> 2 -> 3 -> null**, val = 4
**1 -> 2 -> 3 -> null**Learning Objectives
- Use a dummy head to make deleting the original head identical to deleting an interior node.
- Maintain **prev** as the last kept node and **curr** as the node being inspected.
- Explain why **prev** should not advance when **curr** is deleted.
- Preserve the relative order of all nodes that are not removed.
Intuition
Pattern Recognition
The signal is remove nodes from a singly linked list, especially when the removable node might be the head. Deleting a node requires changing the previous node next pointer, but the original head has no previous node. A dummy head creates a safe previous node before the real list.
The pointer trap is advancing prev after a deletion. If curr is removed, prev.next has been redirected to the next candidate, so prev must stay where it is. This is what correctly removes consecutive matches such as 7 -> 7 -> 7. Advance prev only when curr is kept.
Common mistakes
- ×Handling head deletions with repeated special cases instead of using a dummy node.
- ×Advancing **prev** after deleting **curr**, which skips over consecutive nodes that should also be removed.
- ×Returning the original **head** even though the first one or more nodes may have been deleted.
- ×Changing **curr.next** without first using **prev.next** to bypass the node from the kept chain.
Algorithm Explanation
Key idea
Place dummy before head so every deletion has a previous node. Keep prev at the last node known to remain in the answer and curr at the node under inspection. If curr.val matches val, bypass it with prev.next = curr.next. Otherwise, move prev forward to curr.
Pointer walkthrough
For dummy -> 1 -> 2 -> 6 -> 6 -> 3 -> null with val = 6, start prev = dummy and curr = 1. Since 1 stays, move both pointers forward so prev = 1 and curr = 2. 2 also stays, so prev = 2 and curr = 6.
Now curr matches. Set prev.next from the first 6 to the second 6, producing dummy -> 1 -> 2 -> 6 -> 3 -> null. Do not move prev; it must remain at 2 because the new prev.next may also need deletion. Inspect the second 6, match again, and set prev.next = 3. Only when curr = 3 is kept does prev move to 3. Return dummy.next to handle head removals correctly.
Algorithm
- Create dummy and set dummy.next = head.
- Set prev = dummy and curr = head.
- While curr is not null, compare curr.val with val.
- If they match, bypass curr by setting prev.next = curr.next.
- If they do not match, advance prev to curr.
- In both cases, advance curr to the next node to inspect.
- Return dummy.next as the possibly new head.
Solutions
Solution: Dummy head with prev and curr
Use a dummy node before the list so removing the head uses the same bypass operation as removing any other node. prev tracks the last kept node, while curr scans candidates.
Step-by-step
- Create dummy and link it to head.
- Set previous to dummy and current to head.
- If current.val equals val, set previous.next to current.next and keep previous in place.
- Otherwise move previous to current because the node is kept.
- Move current to current.next and repeat.
- Return dummy.next after the traversal.
O(n)
O(1)
Each node is inspected once, and only a dummy plus two pointers are stored.
Java implementation
Dry Run
Sample input
head = 1 -> 2 -> 6 -> 6 -> 3 -> null, val = 6. Consecutive removals show why prev must stay still after deletion.
| step | prev | curr | action | list from dummy |
|---|---|---|---|---|
| start | dummy | 1 | initialise | **dummy -> 1 -> 2 -> 6 -> 6 -> 3 -> null** |
| 1 | dummy | 1 | keep 1, move prev | **dummy -> 1 -> 2 -> 6 -> 6 -> 3 -> null** |
| 2 | 1 | 2 | keep 2, move prev | **dummy -> 1 -> 2 -> 6 -> 6 -> 3 -> null** |
| 3 | 2 | 6 | delete first 6, prev stays | **dummy -> 1 -> 2 -> 6 -> 3 -> null** |
| 4 | 2 | 6 | delete second 6, prev stays | **dummy -> 1 -> 2 -> 3 -> null** |
| 5 | 2 | 3 | keep 3, move prev | **dummy -> 1 -> 2 -> 3 -> null** |
Returning dummy.next gives 1 -> 2 -> 3 -> null. The same return would also work if the original head had been removed.
Interview Tips
Lead with the dummy node. It shows you understand that head removal should not be a special case. Then be precise about the invariant: prev always points to the last kept node. When curr is deleted, prev does not move because its new next node still has not been inspected.
Likely follow-ups
- How would you remove the nth node from the end in one pass?
- How would you remove duplicates from a sorted linked list?
- How would you partition a list around a value while preserving relative order?
- How would deletion change in a doubly linked list?
Similar Problems
Key Takeaways
- A dummy head makes deleting the original head look like deleting any interior node.
- **prev** means last kept node, not simply the node before the traversal cursor in time.
- After deleting **curr**, keep **prev** fixed so consecutive matches are not skipped.
- Return **dummy.next** because the real head may have changed.