3217. Delete Nodes From Linked List Present in Array
Problem Description
You have a linked list and an array of integers called nums
. Your task is to remove all nodes from the linked list whose values appear in the nums
array, then return the modified linked list.
For example, if you have a linked list with values 1 -> 2 -> 3 -> 4 -> 5
and nums = [2, 4]
, you need to remove the nodes with values 2 and 4. The resulting linked list would be 1 -> 3 -> 5
.
The solution uses a hash set to store all values from nums
for quick lookup. It creates a dummy node that points to the original head of the linked list. This dummy node helps handle edge cases where the head itself needs to be removed.
The algorithm traverses the linked list using a pointer pre
starting from the dummy node. At each step, it checks if the next node's value exists in the hash set s
. If it does, the algorithm "skips" that node by updating pre.next
to point to pre.next.next
, effectively removing the node from the list. If the value doesn't exist in the set, the pointer moves forward normally to the next node.
After processing all nodes, the function returns dummy.next
, which points to the new head of the modified linked list. The time complexity is O(n + m)
where n
is the length of the linked list and m
is the length of the nums
array, while the space complexity is O(m)
for storing the hash set.
Intuition
When we need to remove nodes from a linked list based on certain values, the main challenge is handling the connections properly. If we remove a node, we need to make sure the previous node points to the next node after the removed one, maintaining the chain.
The first insight is that checking if a value exists in the nums
array repeatedly would be inefficient. If we check each node's value against every element in nums
, we'd have O(n * m)
time complexity. Converting nums
to a hash set gives us O(1)
lookup time, significantly improving efficiency.
The second key insight is using a dummy node. Why? Consider if we need to remove the first node (head) of the list. Without a dummy node, we'd need special logic to handle this case - we'd have to update the head reference itself. With a dummy node pointing to the head, removing the first node becomes just like removing any other node: we update dummy.next
to skip it.
The traversal strategy comes naturally: we maintain a pointer to the node before the one we're examining. This way, when we find a node to remove, we already have access to the previous node and can update its next
pointer to skip the unwanted node. We call this pointer pre
(for previous).
As we traverse, we check pre.next.val
(the value of the node we're examining). If it's in our set, we bypass it by setting pre.next = pre.next.next
. If not, we move our pre
pointer forward. This approach ensures we never lose track of the list structure while removing nodes.
Learn more about Linked List patterns.
Solution Approach
Following the hash table approach, we implement the solution in these steps:
Step 1: Create a Hash Set
s = set(nums)
We convert the nums
array into a set s
for O(1) lookup time. This allows us to quickly check if a node's value should be removed.
Step 2: Set Up Dummy Node
pre = dummy = ListNode(next=head)
We create a dummy node and point it to the original head. The variable pre
will traverse the list, while dummy
stays at the beginning to help us return the modified list's head later.
Step 3: Traverse and Remove Nodes
while pre.next:
if pre.next.val in s:
pre.next = pre.next.next
else:
pre = pre.next
We iterate through the list using pre.next
as our condition. This ensures we always have access to the node before the one we're examining.
- If
pre.next.val
is in the sets
, we need to remove this node. We do this by updatingpre.next
to point topre.next.next
, effectively skipping the current node and removing it from the list. - If the value is not in the set, we simply move forward by updating
pre = pre.next
.
Notice that when we remove a node, we don't move pre
forward. This is important because after removal, pre.next
now points to a new node that we haven't checked yet.
Step 4: Return the Modified List
return dummy.next
Since dummy
still points to the position before the original head, dummy.next
gives us the head of the modified list. This works even if the original head was removed.
The algorithm processes each node exactly once, giving us O(n) time complexity for traversal, plus O(m) for creating the set, resulting in overall O(n + m) time complexity.
Ready to land your dream job?
Unlock your dream job with a 5-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.
Given:
- Linked list:
1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6
nums = [6, 2]
- Goal: Remove all nodes with values 6 and 2
Step 1: Create Hash Set
s = {6, 2}
Step 2: Initialize Dummy Node
dummy -> 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6 ↑ pre
We create a dummy node pointing to head (1), and pre
starts at dummy.
Step 3: Traverse and Remove
Iteration 1:
- Check
pre.next.val = 1
- Is 1 in set {6, 2}? No
- Move
pre
forward
dummy -> 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6 ↑ pre
Iteration 2:
- Check
pre.next.val = 2
- Is 2 in set {6, 2}? Yes
- Skip node 2:
pre.next = pre.next.next
dummy -> 1 -----> 6 -> 3 -> 4 -> 5 -> 6 ↑ pre
Note: pre
stays at node 1
Iteration 3:
- Check
pre.next.val = 6
- Is 6 in set {6, 2}? Yes
- Skip node 6:
pre.next = pre.next.next
dummy -> 1 ----------> 3 -> 4 -> 5 -> 6 ↑ pre
Note: pre
still stays at node 1
Iteration 4:
- Check
pre.next.val = 3
- Is 3 in set {6, 2}? No
- Move
pre
forward
dummy -> 1 -> 3 -> 4 -> 5 -> 6 ↑ pre
Iteration 5:
- Check
pre.next.val = 4
- Is 4 in set {6, 2}? No
- Move
pre
forward
dummy -> 1 -> 3 -> 4 -> 5 -> 6 ↑ pre
Iteration 6:
- Check
pre.next.val = 5
- Is 5 in set {6, 2}? No
- Move
pre
forward
dummy -> 1 -> 3 -> 4 -> 5 -> 6 ↑ pre
Iteration 7:
- Check
pre.next.val = 6
- Is 6 in set {6, 2}? Yes
- Skip node 6:
pre.next = pre.next.next
(which is None)
dummy -> 1 -> 3 -> 4 -> 5 -> None ↑ pre
Step 4: Return Result
pre.next
is None, so the loop ends- Return
dummy.next
, which points to node 1 - Final linked list:
1 -> 3 -> 4 -> 5
The algorithm successfully removed all nodes with values 2 and 6 from the original list.
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
7from typing import List, Optional
8
9class Solution:
10 def modifiedList(
11 self, nums: List[int], head: Optional[ListNode]
12 ) -> Optional[ListNode]:
13 """
14 Remove all nodes from the linked list whose values exist in the nums array.
15
16 Args:
17 nums: List of values to be removed from the linked list
18 head: Head of the original linked list
19
20 Returns:
21 Head of the modified linked list with specified values removed
22 """
23 # Convert nums list to set for O(1) lookup time
24 values_to_remove = set(nums)
25
26 # Create a dummy node pointing to head to handle edge cases
27 # (e.g., when the head itself needs to be removed)
28 dummy_node = ListNode(next=head)
29
30 # Initialize pointer to traverse the list
31 current = dummy_node
32
33 # Traverse the linked list
34 while current.next:
35 # Check if the next node's value should be removed
36 if current.next.val in values_to_remove:
37 # Skip the next node by updating the pointer
38 current.next = current.next.next
39 else:
40 # Move to the next node if no removal is needed
41 current = current.next
42
43 # Return the head of the modified list
44 return dummy_node.next
45
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 whose values exist in the given array.
14 * @param nums Array containing values to be removed from the linked list
15 * @param head Head of the original linked list
16 * @return Head of the modified linked list after removing specified nodes
17 */
18 public ListNode modifiedList(int[] nums, ListNode head) {
19 // Create a HashSet for O(1) lookup time of values to be removed
20 Set<Integer> valuesToRemove = new HashSet<>();
21
22 // Add all values from nums array to the HashSet
23 for (int value : nums) {
24 valuesToRemove.add(value);
25 }
26
27 // Create a dummy node pointing to head to handle edge case of removing the first node
28 ListNode dummyNode = new ListNode(0, head);
29
30 // Iterate through the linked list using a predecessor pointer
31 for (ListNode predecessor = dummyNode; predecessor.next != null;) {
32 // Check if the next node's value should be removed
33 if (valuesToRemove.contains(predecessor.next.val)) {
34 // Skip the next node by updating the link
35 predecessor.next = predecessor.next.next;
36 } else {
37 // Move to the next node if no removal is needed
38 predecessor = predecessor.next;
39 }
40 }
41
42 // Return the new head of the modified list
43 return dummyNode.next;
44 }
45}
46
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 ListNode* modifiedList(vector<int>& nums, ListNode* head) {
14 // Create a hash set from nums array for O(1) lookup
15 unordered_set<int> numsSet(nums.begin(), nums.end());
16
17 // Create a dummy node pointing to head to handle edge cases
18 // (e.g., when the first node needs to be removed)
19 ListNode* dummyNode = new ListNode(0, head);
20
21 // Iterate through the linked list using a previous pointer
22 for (ListNode* prevNode = dummyNode; prevNode->next != nullptr;) {
23 // Check if the next node's value exists in the set
24 if (numsSet.count(prevNode->next->val)) {
25 // Remove the next node by updating the pointer
26 prevNode->next = prevNode->next->next;
27 // Note: prevNode stays at the same position for the next iteration
28 } else {
29 // Move to the next node only if no deletion occurred
30 prevNode = prevNode->next;
31 }
32 }
33
34 // Return the modified list (head might have changed)
35 return dummyNode->next;
36 }
37};
38
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * val: number
5 * next: ListNode | null
6 * constructor(val?: number, next?: ListNode | null) {
7 * this.val = (val===undefined ? 0 : val)
8 * this.next = (next===undefined ? null : next)
9 * }
10 * }
11 */
12
13/**
14 * Removes all nodes from the linked list whose values exist in the nums array
15 * @param nums - Array of values to be removed from the linked list
16 * @param head - Head of the linked list
17 * @returns Head of the modified linked list after removing specified nodes
18 */
19function modifiedList(nums: number[], head: ListNode | null): ListNode | null {
20 // Create a set from nums array for O(1) lookup time
21 const valuesToRemove: Set<number> = new Set(nums);
22
23 // Create a dummy node pointing to head to handle edge case of removing the head node
24 const dummyNode: ListNode = new ListNode(0, head);
25
26 // Iterate through the linked list using a predecessor pointer
27 let predecessor: ListNode = dummyNode;
28
29 while (predecessor.next !== null) {
30 // Check if the next node's value should be removed
31 if (valuesToRemove.has(predecessor.next.val)) {
32 // Skip the next node by updating the link
33 predecessor.next = predecessor.next.next;
34 } else {
35 // Move to the next node only if we didn't remove a node
36 predecessor = predecessor.next;
37 }
38 }
39
40 // Return the new head of the list (skipping the dummy node)
41 return dummyNode.next;
42}
43
Time and Space Complexity
Time Complexity: O(n + m)
The algorithm consists of two main operations:
- Converting the array
nums
to a set, which takesO(n)
time wheren
is the length of the arraynums
- Traversing the linked list once using a while loop that visits each node exactly once, which takes
O(m)
time wherem
is the length of the linked listhead
- For each node visited, checking membership in the set
s
isO(1)
on average
Therefore, the total time complexity is O(n) + O(m) = O(n + m)
.
Space Complexity: O(n)
The algorithm uses additional space for:
- The set
s
which stores all unique values fromnums
, requiringO(n)
space wheren
is the length of the arraynums
- A few constant extra variables (
pre
,dummy
) which takeO(1)
space
Therefore, the total space complexity is O(n) + O(1) = O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrectly Advancing the Pointer After Removal
The Problem: A common mistake is to always advance the pointer after processing a node, like this:
while current.next:
if current.next.val in values_to_remove:
current.next = current.next.next
current = current.next # WRONG: Always advancing
This causes a bug because after removing a node, current.next
already points to a new unchecked node. Moving current
forward would skip checking this new node entirely.
Example of the Bug:
Given list: 1 -> 2 -> 3 -> 4
and nums = [2, 3]
- Start:
current
points to dummy (before 1) - Check node 1: not in set, move to node 1
- Check node 2: in set, remove it (now:
1 -> 3 -> 4
) - If we advance
current
here, we'd skip checking node 3! - Result:
1 -> 3 -> 4
(incorrect, node 3 should be removed)
The Solution: Only advance the pointer when no removal occurs:
while current.next:
if current.next.val in values_to_remove:
current.next = current.next.next
# Don't advance current here!
else:
current = current.next # Only advance when keeping the node
Pitfall 2: Not Using a Dummy Node
The Problem: Trying to handle head removal without a dummy node leads to complex edge case handling:
# Without dummy node - complicated and error-prone
if head and head.val in values_to_remove:
head = head.next
current = head
while current and current.next:
if current.next.val in values_to_remove:
current.next = current.next.next
else:
current = current.next
return head
This approach requires special handling for:
- Empty list
- Single node that needs removal
- Multiple consecutive removals at the start
The Solution: Always use a dummy node to simplify the logic:
dummy_node = ListNode(next=head)
current = dummy_node
# ... rest of the logic
return dummy_node.next
The dummy node ensures uniform handling of all nodes, including the head, without special cases.
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!