[Easy] 203. Remove Linked List Elements
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.
Examples
Example 1:
Input: head = [1,2,6,3,4,5,6], val = 6
Output: [1,2,3,4,5]
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 <= 500 <= val <= 50
Thinking Process
- Dummy Node: Using a dummy node simplifies edge cases, especially when the head needs to be removed
- Draw pointers before rewriting links.
- Dummy head simplifies insert/delete at the head.
- Slow/fast pointers find middle or detect cycles in one pass.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Iterative pointer walk (this problem) | O(n) | O(1) | Traversal, insertion |
| Dummy head node | O(n) | O(1) | Simplify head-edge cases |
| Reversal (3-pointer) | O(n) | O(1) | Reverse sublist or full list |
| Slow/fast pointers | O(n) | O(1) | Middle, cycle, merge lists |
Solution
Time Complexity: O(n) - We visit each node once
Space Complexity: O(1) - Only using constant extra space
The key insight is to use a dummy node to handle edge cases where the head itself needs to be removed. We traverse the list with two pointers: prev (previous node) and curr (current node), removing nodes that match the target value.
Solution: Iterative with Dummy Node
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if(head == nullptr) return head;
ListNode dummy = ListNode(0, head);
ListNode* prev = &dummy;
ListNode* curr = head;
ListNode* toDelete = nullptr;
while(curr != nullptr) {
if (curr->val == val) {
prev->next = curr->next;
toDelete = curr;
} else {
prev = curr;
}
curr = curr->next;
if(toDelete != nullptr) {
delete toDelete;
toDelete = nullptr;
}
}
ListNode *ret = dummy.next;
return ret;
}
};
Solution Explanation
Approach: Iterative pointer walk (this problem)
Key idea: 1. Dummy Node: Using a dummy node simplifies edge cases, especially when the head needs to be removed
How the code works:
- Dummy Node: Using a dummy node simplifies edge cases, especially when the head needs to be removed
- Draw pointers before rewriting links.
- Dummy head simplifies insert/delete at the head.
- Slow/fast pointers find middle or detect cycles in one pass.
Walkthrough — input head = [1,2,6,3,4,5,6], val = 6, expected output [1,2,3,4,5]:
- Initialize variables from the problem setup.
- Apply the main loop / recursion until the condition is met.
- Confirm the result matches the expected output.
| Approach | Time | Space | Pros | Cons | |———-|——|——-|——|——| | Iterative with Dummy | O(n) | O(1) | Space efficient, handles all cases | Requires memory management | | Recursive | O(n) | O(n) | Elegant, concise | Stack overflow risk for long lists | | Simplified Iterative | O(n) | O(1) | Simple, no memory management | Doesn’t free memory (fine for LeetCode) |
Algorithm Breakdown
ListNode* removeElements(ListNode* head, int val) {
// Handle empty list
if(head == nullptr) return head;
// Create dummy node to simplify edge cases
ListNode dummy(0, head);
ListNode* prev = &dummy; // Previous valid node
ListNode* curr = head; // Current node being checked
ListNode* toDelete = nullptr;
while(curr != nullptr) {
if (curr->val == val) {
// Skip the current node
prev->next = curr->next;
toDelete = curr; // Mark for deletion
} else {
// Move prev forward only when we keep the node
prev = curr;
}
// Move to next node
curr = curr->next;
// Delete removed node
if(toDelete != nullptr) {
delete toDelete;
toDelete = nullptr;
}
}
return dummy.next; // Return new head
}
Common Mistakes
- Empty list:
head = []→ return[] - Head needs removal:
head = [7,7,7,7], val = 7→ return[] - All nodes removed:
head = [1,1,1], val = 1→ return[] - No nodes removed:
head = [1,2,3], val = 4→ return[1,2,3] -
Remove from middle:
head = [1,2,3,2,4], val = 2→ return[1,3,4] - Not using dummy node: Makes it harder to handle head removal
- Incorrect pointer updates: Forgetting to update
prevonly when keeping a node - Memory leaks: Not deleting removed nodes
- Returning wrong pointer: Should return
dummy.next, nothead - Null pointer dereference: Not checking if
headisnullptrfirst
Complexity
| Approach | Time | Space | Pros | Cons | |———-|——|——-|——|——| | Iterative with Dummy | O(n) | O(1) | Space efficient, handles all cases | Requires memory management | | Recursive | O(n) | O(n) | Elegant, concise | Stack overflow risk for long lists | | Simplified Iterative | O(n) | O(1) | Simple, no memory management | Doesn’t free memory (fine for LeetCode) |
Related Problems
- 83. Remove Duplicates from Sorted List - Remove duplicates
- 82. Remove Duplicates from Sorted List II - Remove all duplicates
- 237. Delete Node in a Linked List - Delete without head reference
- 19. Remove Nth Node From End of List - Remove specific node
Optimization Notes
- Dummy Node Pattern: Essential for simplifying linked list deletion problems
- Memory Management: In production code, always delete removed nodes
- Early Termination: Could optimize by checking if list is empty first
- Pointer Safety: Always check for
nullptrbefore dereferencing
Key Takeaways
- Dummy Node: Using a dummy node simplifies edge cases, especially when the head needs to be removed
- Two Pointers:
prevtracks the previous valid node,currtraverses the list - Memory Management: Properly delete removed nodes to prevent memory leaks
- Pointer Updates: Only update
prevwhen we don’t remove a node; otherwise,prevstays the same
References
- LC 203: Remove Linked List Elements on LeetCode
- LeetCode Discuss — LC 203: Remove Linked List Elements
- LeetCode Editorial (may require premium)