89. Remove Linked List Elements
π LeetCode Problem: 203. Remove Linked List Elements
π Difficulty: Easy
π·οΈ Topics: Linked List, Recursion
Problem Statement
Given the head of a linked list and an integer val, remove all the nodes of the linked list that has Node.val == val, and return the new head.
Example 1:
Input: head = [1,2,6,3,4,5,6], val = 6
Output: [1,2,3,4,5]
Visual:
Before: 1 β 2 β 6 β 3 β 4 β 5 β 6 β null
After: 1 β 2 β 3 β 4 β 5 β null
(removed both 6's)
Example 2:
Input: head = [], val = 1
Output: []
Example 3:
Input: head = [7,7,7,7], val = 7
Output: []
Constraints:
- The number of nodes in the list is in the range [0, 10^4].
- 1 <= Node.val <= 50
- 0 <= val <= 50
π ELI5: The Simple Idea!
Think of removing people from a queue:
Queue: [Alice, Bob, Charlie, Bob, Dave, Bob]
Task: Remove all "Bob"s
Method 1: Without marking the front
- Need special handling for first person
- If first is Bob, move everyone forward
- Then check rest one by one
Method 2: Add a marker at the front
- Put a "START" marker before Alice
- Now you can check everyone uniformly
- "Does next person = Bob? Skip them!"
- No special case for first person!
Result: [Alice, Charlie, Dave]
The Dummy Node Trick:
Without dummy:
head = [6, 1, 2, 6], remove 6
First is 6! Special case needed!
With dummy:
dummy β 6 β 1 β 2 β 6
Now can check all uniformly:
"Is next node 6? Skip it!"
π¨ Visual Understanding
The Removal Process
Initial: head = [1, 2, 6, 3, 4, 5, 6], val = 6
Step 1: Add dummy node
dummy β 1 β 2 β 6 β 3 β 4 β 5 β 6 β null
β
current
Step 2: Check current.next
current at dummy, next is 1
1 == 6? No, keep it
Move current forward
dummy β 1 β 2 β 6 β 3 β 4 β 5 β 6 β null
β
current
Step 3: Check current.next
current at 1, next is 2
2 == 6? No, keep it
Move current forward
dummy β 1 β 2 β 6 β 3 β 4 β 5 β 6 β null
β
current
Step 4: Check current.next
current at 2, next is 6
6 == 6? YES! Skip it!
current.next = current.next.next
dummy β 1 β 2 β 3 β 4 β 5 β 6 β null
β (skipped 6)
current
DON'T move current (need to check new next)
Step 5: Check current.next (again)
current still at 2, next is now 3
3 == 6? No, keep it
Move current forward
dummy β 1 β 2 β 3 β 4 β 5 β 6 β null
β
current
... continue until end ...
Step N: Final result
current.next = null, done!
dummy β 1 β 2 β 3 β 4 β 5 β null
Return: dummy.next (skip dummy)
Result: [1, 2, 3, 4, 5]
π― Approach 1: Dummy Node (Iterative) β
The Recommended Solution
Why Dummy Node?
Problem: Head might need to be removed!
Without dummy:
[6, 6, 1, 2], val = 6
Need to handle: "while head == 6, head = head.next"
Then handle rest differently
Complex!
With dummy:
dummy β 6 β 6 β 1 β 2
Uniform logic: "if next == 6, skip it"
Simple!
Implementation
/**
* Iterative with dummy node
* Time: O(n), Space: O(1)
*/
public ListNode removeElements(ListNode head, int val) {
// Create dummy node to handle head removal
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode current = dummy;
// Traverse and remove matching nodes
while (current.next != null) {
if (current.next.val == val) {
// Skip the next node
current.next = current.next.next;
// DON'T move current - check new next node
} else {
// Keep the next node, move forward
current = current.next;
}
}
return dummy.next; // Return actual head (skip dummy)
}
β° Time: O(n) - Visit each node once
πΎ Space: O(1) - Only dummy and current pointer
π Super Detailed Dry Run
Example: head = [1, 2, 6, 3, 4, 5, 6], val = 6
Goal: Remove all 6's β [1, 2, 3, 4, 5]
Initial state:
head β 1 β 2 β 6 β 3 β 4 β 5 β 6 β null
dummy = new node(0)
dummy.next = head
dummy β 1 β 2 β 6 β 3 β 4 β 5 β 6 β null
β
current
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Iteration 1: Check node 1
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
State:
current = dummy
current.next = node 1
Condition: current.next != null (1) β
Check: current.next.val == val?
1 == 6? No
Action: Keep node, move forward
current = current.next
State after:
dummy β 1 β 2 β 6 β 3 β 4 β 5 β 6 β null
β
current
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Iteration 2: Check node 2
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
State:
current = node 1
current.next = node 2
Condition: current.next != null (2) β
Check: current.next.val == val?
2 == 6? No
Action: Keep node, move forward
current = current.next
State after:
dummy β 1 β 2 β 6 β 3 β 4 β 5 β 6 β null
β
current
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Iteration 3: Check node 6 (FIRST MATCH!)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
State:
current = node 2
current.next = node 6
Condition: current.next != null (6) β
Check: current.next.val == val?
6 == 6? YES!
Action: Skip node
current.next = current.next.next
Before:
dummy β 1 β 2 β 6 β 3 β 4 β 5 β 6 β null
β β
current (skip this)
After:
dummy β 1 β 2 β 3 β 4 β 5 β 6 β null
β β
current (now points here)
DON'T move current! Need to check new next!
State after:
dummy β 1 β 2 β 3 β 4 β 5 β 6 β null
β
current (stays here)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Iteration 4: Check node 3
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
State:
current = node 2 (same as before!)
current.next = node 3 (new next after removal)
Condition: current.next != null (3) β
Check: current.next.val == val?
3 == 6? No
Action: Keep node, move forward
current = current.next
State after:
dummy β 1 β 2 β 3 β 4 β 5 β 6 β null
β
current
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Iteration 5: Check node 4
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
State:
current = node 3
current.next = node 4
Condition: current.next != null (4) β
Check: current.next.val == val?
4 == 6? No
Action: Keep node, move forward
current = current.next
State after:
dummy β 1 β 2 β 3 β 4 β 5 β 6 β null
β
current
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Iteration 6: Check node 5
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
State:
current = node 4
current.next = node 5
Condition: current.next != null (5) β
Check: current.next.val == val?
5 == 6? No
Action: Keep node, move forward
current = current.next
State after:
dummy β 1 β 2 β 3 β 4 β 5 β 6 β null
β
current
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Iteration 7: Check node 6 (SECOND MATCH!)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
State:
current = node 5
current.next = node 6
Condition: current.next != null (6) β
Check: current.next.val == val?
6 == 6? YES!
Action: Skip node
current.next = current.next.next
Before:
dummy β 1 β 2 β 3 β 4 β 5 β 6 β null
β β
current (skip this)
After:
dummy β 1 β 2 β 3 β 4 β 5 β null
β
current
State after:
dummy β 1 β 2 β 3 β 4 β 5 β null
β
current
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Iteration 8: Check end
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
State:
current = node 5
current.next = null
Condition: current.next != null (null) β
Exit loop!
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Return Result
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Return: dummy.next
Final list:
1 β 2 β 3 β 4 β 5 β null β
Result: [1, 2, 3, 4, 5]
π― Approach 2: Without Dummy Node
Direct Manipulation (More Complex)
/**
* Without dummy node
* Time: O(n), Space: O(1)
* More complex due to head handling
*/
public ListNode removeElements(ListNode head, int val) {
// Remove from head while needed
while (head != null && head.val == val) {
head = head.next;
}
// If list became empty
if (head == null) {
return null;
}
// Remove from rest of list
ListNode current = head;
while (current.next != null) {
if (current.next.val == val) {
current.next = current.next.next;
} else {
current = current.next;
}
}
return head;
}
Why This is More Complex:
Extra logic needed:
1. Special loop for head removal
2. Check if list becomes empty
3. Different handling for head vs rest
Dummy node eliminates all this!
π― Approach 3: Recursive β
Elegant Recursive Solution
Core Logic:
Recursively remove from rest of list
Then decide about current node
Base case: null β return null
Recursive case:
1. Process rest of list
2. If current should be removed, skip it
3. Otherwise, keep it
Implementation
/**
* Recursive approach
* Time: O(n), Space: O(n) - recursion stack
*/
public ListNode removeElements(ListNode head, int val) {
// Base case
if (head == null) {
return null;
}
// Recursively remove from rest
head.next = removeElements(head.next, val);
// Decide about current node
return (head.val == val) ? head.next : head;
}
β° Time: O(n) - Visit each node once
πΎ Space: O(n) - Recursion call stack
π Recursive Dry Run
Example: head = [1, 6, 2], val = 6
Goal: Remove 6 β [1, 2]
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Call Stack Building
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
removeElements([1, 6, 2], 6)
head = 1, val = 6
head.next = removeElements([6, 2], 6)
β
removeElements([6, 2], 6)
head = 6, val = 6
head.next = removeElements([2], 6)
β
removeElements([2], 6)
head = 2, val = 6
head.next = removeElements(null, 6)
β
removeElements(null, 6)
head = null
BASE CASE: return null
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Unwinding Stack
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Back to: removeElements([2], 6)
head.next = null
head.val (2) == val (6)? No
Return: head (2)
2 β null
Back to: removeElements([6, 2], 6)
head.next = 2 β null
head.val (6) == val (6)? YES!
Return: head.next (skip 6)
Return: 2 β null
Back to: removeElements([1, 6, 2], 6)
head.next = 2 β null
head.val (1) == val (6)? No
Return: head
1 β 2 β null
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Result
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Final list: 1 β 2 β null β
Visual Call Stack:
removeElements([1,6,2], 6) β Return 1β2
β
removeElements([6,2], 6) β Return 2 (skip 6)
β
removeElements([2], 6) β Return 2
β
removeElements(null, 6) β Return null
Building back up:
[2]: 2.next = null, 2 != 6 β keep 2
[6,2]: 6.next = 2, 6 == 6 β skip, return 2
[1,6,2]: 1.next = 2, 1 != 6 β keep 1β2
π― Comparison of Approaches
Approach Time Space Code Recommended
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Dummy Node O(n) O(1) Clean βββββ
Without Dummy O(n) O(1) Complex ββ
Recursive O(n) O(n) Elegant ββββ
Recommended: Dummy Node
β Simplest code
β Uniform handling
β O(1) space
β Most common in interviews
Recursive:
β Very elegant
β Shows recursion skill
β Uses stack space
β Good alternative answer
β οΈ Common Mistakes
Mistake 1: Moving current when removing
// β WRONG - Skips next node check!
if (current.next.val == val) {
current.next = current.next.next;
current = current.next; // DON'T MOVE!
}
// β CORRECT - Stay to check new next
if (current.next.val == val) {
current.next = current.next.next;
// Don't move! New next might also need removal
}
Mistake 2: Checking current instead of current.next
// β WRONG - Can't remove current itself!
if (current.val == val) {
// How to remove current? Lost reference!
}
// β CORRECT - Check next node
if (current.next.val == val) {
current.next = current.next.next; // Can remove!
}
Mistake 3: Not using dummy for head removal
// β WRONG - Head removal is messy
if (head.val == val) {
// Special case!
}
// Different logic for rest...
// β CORRECT - Dummy handles all
ListNode dummy = new ListNode(0);
dummy.next = head;
// Uniform logic for all nodes!
Mistake 4: Returning dummy instead of dummy.next
// β WRONG - Includes dummy in result!
return dummy;
// β CORRECT - Skip dummy
return dummy.next;
Mistake 5: Not handling all-match case
// Example: [7, 7, 7], val = 7
// Result should be empty list (null)
// β Code handles it naturally!
// After all removals, dummy.next = null
return dummy.next; // Returns null β
π― Pattern Recognition
Problem Type: Remove Nodes by Condition
Core Pattern: Dummy Node + Filter
When to Apply:
β Remove nodes matching value
β Remove nodes matching condition
β Filter linked list
β Head might be removed
Recognition Keywords:
- "remove"
- "delete"
- "filter"
- "nodes that satisfy"
- "exclude"
Similar Problems:
- Remove Duplicates from Sorted List (LC 83)
- Remove Duplicates from Sorted List II (LC 82)
- Delete Node in a Linked List (LC 237)
- Remove Nth Node From End (LC 19)
Key Components:
ββββββββββββββββββββββββββββββββββββββββββββββ
β Dummy Node: Handle head removal β
β Check Next: current.next.val == val β
β Skip Node: current.next = current.next.nextβ
β DON'T Move: Stay when removing β
β Time: O(n), Space: O(1) β
ββββββββββββββββββββββββββββββββββββββββββββββ
π§ Interview Strategy
Step 1: "I see this requires removing nodes by value"
Step 2: "I'll use dummy node to handle head removal"
Step 3: "Check next node, skip if matches"
Step 4: "Important: don't move when removing"
Key Points to Mention:
- Dummy node simplifies head removal
- Check current.next (not current)
- Don't move pointer when removing
- Stay to check new next node
- Handle consecutive matches
- Time: O(n), Space: O(1)
Walk Through Example:
"For [1,2,6,3,6], val=6, I add a dummy node.
Check each next node.
When next is 6, skip it by: current.next = current.next.next
Don't move current - new next might also be 6!
When next is not 6, move current forward.
Return dummy.next."
Edge Cases to Mention:
β Empty list β returns null
β All nodes match β returns null
β No nodes match β returns original
β Head matches β dummy handles it
β Consecutive matches β stay and check
π Quick Revision Notes
π― Core Concept:
Remove Linked List Elements: Use dummy node to simplify head removal. Check current.next (not current). If matches, skip it: current.next = current.next.next. Critical: DON'T move current after removal - stay to check new next! Only move when keeping node.
β‘ Quick Implementation:
import java.util.Arrays;
import java.util.List;
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
class Solution {
public static ListNode create(List<Integer> list) {
ListNode dummy = new ListNode();
ListNode curr = dummy;
for(int num : list) {
curr.next = new ListNode(num);
curr = curr.next;
}
return dummy.next;
}
public static void print(ListNode head) {
ListNode curr = head;
while (curr != null) {
System.out.print(curr.val + " -> ");
curr = curr.next;
}
System.out.println();
}
public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode();
dummy.next = head;
ListNode curr = dummy;
// Intuitive on paper by having a dummy node.
// Dummy node to handle if the value to be removed is on the first on the list.
while (curr != null) {
if(curr.next != null && curr.next.val == val) {
curr.next = curr.next.next; // skip (delink) curr.next and attach next to the existing one
} else {
curr = curr.next; // else move normally
}
}
return dummy.next;
}
public static void main(String[] args) {
Solution t = new Solution();
ListNode list1 = create(Arrays.asList(1,2,6,3,4,5,6));
print(t.removeElements(list1, 6));
print(t.removeElements(create(Arrays.asList(1)), 1)); // empty
print(t.removeElements(create(Arrays.asList(1)), 6)); // 1
print(t.removeElements(create(Arrays.asList(1, 2)), 6)); // 1, 2
print(t.removeElements(create(Arrays.asList(1, 6)), 6)); // 1
print(t.removeElements(create(Arrays.asList(1, 6)), 1)); // 6
print(t.removeElements(create(Arrays.asList(1, 1, 6)), 1)); // 6
print(t.removeElements(create(Arrays.asList(1, 1, 1, 6)), 1)); // 6
print(t.removeElements(create(Arrays.asList(1, 1, 1, 6, 6)), 6)); // 1,1,1
print(t.removeElements(create(Arrays.asList(1, 1, 1, 6, 6, 6)), 6)); // 1,1,1
print(t.removeElements(create(Arrays.asList(1, 1, 1, 6, 6, 6, 7, 8)), 6)); // 1,1,1,7,8
}
}
π Key Insights:
- Pattern: Dummy Node + Filter
- Dummy: Handles head removal elegantly
- Check: current.next (not current)
- Remove: Skip via current.next.next
- DON'T Move: After removal (critical!)
- Time: O(n), Space: O(1) β
πͺ Memory Aid:
"Dummy β Check Next β Match? Skip & Stay : Keep & Move!"
"Skip = Stay, Keep = Move!" β¨
β οΈ The Golden Rule:
When removing:
current.next = current.next.next
DON'T move current!
When keeping:
current = current.next
Move forward!
Why?
New next might also need removal!
[2 β 6 β 6 β 3]
β
Remove first 6
[2 β 6 β 3]
β
Stay here! Check new next (also 6)
π§ͺ Edge Cases
Case 1: Empty list
Input: head = null, val = 1
Output: null
Handled: Loop never runs, dummy.next = null
Case 2: All nodes match
Input: head = [7,7,7,7], val = 7
Output: null
Handled: All removed, dummy.next = null
Case 3: No nodes match
Input: head = [1,2,3], val = 4
Output: [1,2,3]
Handled: Nothing removed, returns original
Case 4: Head matches
Input: head = [6,1,2], val = 6
Output: [1,2]
Handled: Dummy allows uniform removal
Case 5: Consecutive matches
Input: head = [1,6,6,6,2], val = 6
Output: [1,2]
Handled: Stay and check after each removal
Case 6: Only head remains
Input: head = [1,6,6,6], val = 6
Output: [1]
Handled: Correctly
All handled perfectly! β
Related Problems
- Remove Duplicates from Sorted List (LC 83) - Similar pattern
- Remove Duplicates from Sorted List II (LC 82) - Remove all duplicates
- Delete Node in a Linked List (LC 237) - Different approach
- Remove Nth Node From End (LC 19) - Position-based removal
Happy practicing! π―
Note: The dummy node pattern is your best friend for any problem involving node removal! This pattern appears in dozens of linked list problems. Master the "Skip & Stay, Keep & Move" rule and you'll never get confused! πͺβ¨