203. Remove Linked List Elements
Problem Description
You are given the head of a singly linked list and an integer value val
. Your task is to remove all nodes from the linked list that have a value equal to val
and return the head of the modified linked list.
The problem requires you to traverse through the linked list and delete every node whose value matches the given target value. After removing all such nodes, you need to return the new head of the list (which might be different from the original head if the original head node itself needs to be removed).
For example:
- If the linked list is
1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6
andval = 6
, the result should be1 -> 2 -> 3 -> 4 -> 5
- If the linked list is
7 -> 7 -> 7 -> 7
andval = 7
, the result should be an empty list (returnnull
)
The solution uses a dummy node technique to handle edge cases where the head node itself needs to be removed. By creating a dummy node that points to the original head, we can treat all nodes uniformly without special handling for the head node. The algorithm maintains a pre
pointer that checks the next node's value. If the next node's value equals val
, it skips that node by adjusting the link (pre.next = pre.next.next
). Otherwise, it moves the pre
pointer forward. Finally, it returns dummy.next
, which is the new head of the modified list.
Intuition
When removing nodes from a linked list, the main challenge is maintaining proper connections between the remaining nodes. We need to "skip over" the nodes we want to remove by redirecting pointers.
The key insight is that to remove a node, we need access to the node before it. This is because in a singly linked list, we can only modify the next
pointer of a node to skip over unwanted nodes. If we're at node A and want to remove node B (where A points to B), we simply make A point to C instead (where B was pointing to C).
However, this creates a special case problem: what if the head node itself needs to be removed? We don't have a previous node to work with. We could handle this with special conditional logic, checking if the head matches the target value and updating it separately. But this leads to messy code with multiple cases.
The elegant solution is to use a dummy node. By creating a temporary node that points to the head, we artificially create a "previous node" for the head. Now every node in the original list, including the head, has a predecessor. This uniform structure allows us to use the same removal logic for all nodes without special cases.
We maintain a pointer pre
that always stays one step behind the node we're examining. At each step, we check pre.next.val
:
- If it matches our target value, we skip it by setting
pre.next = pre.next.next
- If it doesn't match, we advance
pre
topre.next
This way, pre
always stays on a "safe" node (one we're keeping), and we can safely manipulate its next
pointer to remove unwanted nodes. After processing the entire list, dummy.next
gives us the new head of the cleaned list.
Solution Approach
The implementation uses a dummy node pattern to simplify the linked list manipulation. Here's the step-by-step breakdown:
-
Create a Dummy Node:
dummy = ListNode(-1, head)
We create a dummy node with an arbitrary value (-1) that points to the original head. This dummy node serves as a stable reference point and handles the edge case where the head itself needs to be removed.
-
Initialize the Traversal Pointer:
pre = dummy
We set
pre
to start at the dummy node. This pointer will always stay one node behind the node we're examining, allowing us to modify connections. -
Traverse the List:
while pre.next:
We continue traversing as long as there's a next node to examine. Note that we check
pre.next
instead ofpre
because we need to look ahead to decide whether to remove the next node. -
Check and Remove Nodes:
if pre.next.val != val: pre = pre.next else: pre.next = pre.next.next
- If the next node's value doesn't match
val
, we keep it and movepre
forward - If the next node's value matches
val
, we remove it by bypassing it:pre.next
now points topre.next.next
, effectively removing the node from the chain - Notice that when we remove a node, we don't move
pre
forward because we need to check the newpre.next
in the next iteration
- If the next node's value doesn't match
-
Return the New Head:
return dummy.next
After processing all nodes,
dummy.next
points to the new head of the modified list. This could be the original head (if it wasn't removed) or a different node (if the original head was removed).
Time Complexity: O(n)
where n is the number of nodes in the linked list, as we visit each node exactly once.
Space Complexity: O(1)
as we only use a constant amount of extra space for the dummy node and pointer variables.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the algorithm works step by step.
Example: Remove all nodes with value 2 from the linked list 1 -> 2 -> 2 -> 3 -> 2 -> 4
Initial Setup:
dummy(-1) -> 1 -> 2 -> 2 -> 3 -> 2 -> 4
pre = dummy
Step 1: Check pre.next.val
(which is 1)
- 1 ≠ 2, so move
pre
forward
dummy(-1) -> 1 -> 2 -> 2 -> 3 -> 2 -> 4
pre
Step 2: Check pre.next.val
(which is 2)
- 2 = 2, so remove this node by setting
pre.next = pre.next.next
dummy(-1) -> 1 -----> 2 -> 3 -> 2 -> 4
pre
Step 3: Check pre.next.val
(which is still 2)
- 2 = 2, so remove this node again
dummy(-1) -> 1 -----------> 3 -> 2 -> 4
pre
Step 4: Check pre.next.val
(which is 3)
- 3 ≠ 2, so move
pre
forward
dummy(-1) -> 1 -> 3 -> 2 -> 4
pre
Step 5: Check pre.next.val
(which is 2)
- 2 = 2, so remove this node
dummy(-1) -> 1 -> 3 -----> 4
pre
Step 6: Check pre.next.val
(which is 4)
- 4 ≠ 2, so move
pre
forward
dummy(-1) -> 1 -> 3 -> 4
pre
Step 7: pre.next
is null
, so we're done
Result: Return dummy.next
, which points to the head of the modified list: 1 -> 3 -> 4
Notice how the dummy node allowed us to handle all removals uniformly, even if we had needed to remove the first node. The key insight is that pre
stays on "safe" nodes we're keeping, while we look ahead to decide whether to skip the next node.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7class Solution:
8 def removeElements(self, head: ListNode, val: int) -> ListNode:
9 # Create a dummy node pointing to the head
10 # This handles the edge case where the head itself needs to be removed
11 dummy_node = ListNode(-1, head)
12
13 # Initialize pointer to traverse the list
14 # Starting from dummy node ensures we can modify the head if needed
15 previous = dummy_node
16
17 # Traverse the list until we reach the end
18 while previous.next:
19 # Check if the next node's value matches the target value
20 if previous.next.val != val:
21 # If not matching, move the pointer forward
22 previous = previous.next
23 else:
24 # If matching, skip the next node by updating the link
25 # This effectively removes the node from the list
26 previous.next = previous.next.next
27
28 # Return the new head (dummy's next pointer)
29 return dummy_node.next
30
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 /**
13 * Removes all nodes from the linked list that have the specified value.
14 *
15 * @param head The head of the linked list
16 * @param val The value to be removed from the list
17 * @return The head of the modified linked list
18 */
19 public ListNode removeElements(ListNode head, int val) {
20 // Create a dummy node pointing to the head to handle edge cases
21 // where the head itself needs to be removed
22 ListNode dummyNode = new ListNode(-1, head);
23
24 // Initialize pointer to track the previous node
25 ListNode previousNode = dummyNode;
26
27 // Traverse the linked list
28 while (previousNode.next != null) {
29 // Check if the next node's value matches the target value
30 if (previousNode.next.val != val) {
31 // Move to the next node if value doesn't match
32 previousNode = previousNode.next;
33 } else {
34 // Skip the node with matching value by updating the link
35 previousNode.next = previousNode.next.next;
36 }
37 }
38
39 // Return the actual head (skipping the dummy node)
40 return dummyNode.next;
41 }
42}
43
1class Solution {
2public:
3 ListNode* removeElements(ListNode* head, int val) {
4 // Create a dummy node pointing to the head to handle edge cases
5 // (e.g., when we need to remove the head node itself)
6 ListNode* dummyNode = new ListNode();
7 dummyNode->next = head;
8
9 // Use current pointer to traverse the list
10 // Start from dummy node to check the next node
11 ListNode* current = dummyNode;
12
13 // Traverse the list until we reach the end
14 while (current->next != nullptr) {
15 // Check if the next node's value matches the target value
16 if (current->next->val == val) {
17 // Skip the node with matching value by updating the pointer
18 // This effectively removes the node from the list
19 current->next = current->next->next;
20 } else {
21 // Move to the next node only if we didn't remove a node
22 // This ensures we don't skip checking newly connected nodes
23 current = current->next;
24 }
25 }
26
27 // Return the new head (dummy->next handles the case where original head was removed)
28 return dummyNode->next;
29 }
30};
31
1/**
2 * Definition for singly-linked list node
3 */
4class ListNode {
5 val: number;
6 next: ListNode | null;
7
8 constructor(val?: number, next?: ListNode | null) {
9 this.val = (val === undefined ? 0 : val);
10 this.next = (next === undefined ? null : next);
11 }
12}
13
14/**
15 * Removes all nodes from a linked list that have a specific value
16 * @param head - The head of the linked list
17 * @param val - The value to remove from the list
18 * @returns The head of the modified linked list
19 */
20function removeElements(head: ListNode | null, val: number): ListNode | null {
21 // Create a dummy node pointing to head to handle edge cases
22 // where the head itself needs to be removed
23 const dummyNode: ListNode = new ListNode(0, head);
24
25 // Initialize current pointer to traverse the list
26 let currentNode: ListNode = dummyNode;
27
28 // Traverse the linked list
29 while (currentNode.next !== null) {
30 // Check if the next node's value matches the target value
31 if (currentNode.next.val === val) {
32 // Skip the next node by updating the pointer
33 currentNode.next = currentNode.next.next;
34 } else {
35 // Move to the next node if no removal is needed
36 currentNode = currentNode.next;
37 }
38 }
39
40 // Return the new head (skipping the dummy node)
41 return dummyNode.next;
42}
43
Time and Space Complexity
Time Complexity: O(n)
, where n
is the number of nodes in the linked list. The algorithm traverses the linked list once using a single while loop. In each iteration, it either moves the pointer forward or removes a node, but each node is visited at most once. Therefore, the time complexity is linear.
Space Complexity: O(1)
. The algorithm only uses a constant amount of extra space regardless of the input size. It creates a dummy node and uses two pointer variables (dummy
and pre
), which occupy constant space. The removal operation is done in-place by modifying the pointers of the existing nodes without creating any new nodes or using additional data structures that scale with input size.
Common Pitfalls
1. Forgetting to Handle Head Removal
One of the most common mistakes is trying to handle the linked list without considering that the head node itself might need to be removed. This leads to complex conditional logic and potential bugs.
Incorrect Approach:
def removeElements(self, head: ListNode, val: int) -> ListNode:
# This fails when all leading nodes need to be removed
current = head
while current and current.next:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
# Doesn't handle if head.val == val
return head
Solution: Always use a dummy node to uniformly handle all nodes including the head.
2. Moving the Pointer After Deletion
A critical mistake is advancing the previous
pointer after removing a node. When you remove a node, the previous.next
already points to a new node that hasn't been checked yet.
Incorrect Approach:
while previous.next:
if previous.next.val == val:
previous.next = previous.next.next
previous = previous.next # WRONG! This skips checking the new next node
else:
previous = previous.next
Solution: Only move the pointer forward when you're NOT removing a node. After removal, stay at the same position to check the new next node.
3. Not Checking for None Before Accessing next.next
When removing a node, accessing previous.next.next
without ensuring it exists can cause AttributeError.
Incorrect Approach:
while previous.next:
if previous.next.val == val:
# Dangerous if previous.next is the last node
previous.next = previous.next.next # previous.next.next might be None
Solution: The code actually handles this correctly because when previous.next.next
is None, it's valid to assign None to previous.next
, which properly terminates the list.
4. Using current Instead of previous Pointer
Some implementations try to use a current
pointer to traverse, which makes it impossible to remove the current node since you've lost the reference to the previous node.
Incorrect Approach:
current = head
while current:
if current.val == val:
# Can't remove current without access to previous node!
pass
current = current.next
Solution: Always maintain a pointer to the node BEFORE the one you might need to remove, allowing you to update the links properly.
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!