Problem overview
The method Remove receives the head of a singly linked list and an integer val. It must delete every node whose val field equals the given value and return the head of the resulting list. For [1, 2, 6, 3, 4, 5, 6] with val = 6, the answer is [1, 2, 3, 4, 5].
The list is changed in place, in O(n) time with O(1) extra space. The returned head can be a different node from the one passed in, for example when the first nodes are the ones being removed.
Approach: always stand one node before the candidate
To delete a node in a singly linked list, you change the next of the node before it so that it skips over the unwanted node: before.next = before.next.next. So the loop should stand on the node before the one it is examining, and look at node.next.val.
That works for every node except the head, because the head has no node before it. Handling the head separately means a loop just to drop leading matches, then a second loop for the rest. A dummy node avoids that. Create an empty node and point its next at the head. Now every real node, including the head, has a predecessor, and one loop handles them all. At the end the cleaned list starts at dummy.next, which is the new head whether or not the old head was removed.
There is one subtlety: after removing a node, the walker must not move. The node that took its place might also match. In 1 -> 6 -> 6 -> 2 with val = 6, standing on 1 you remove the first 6 and the second 6 is now node.next. Only when node.next is a keeper do you advance.
Walking through [1, 2, 6, 3, 4, 5, 6]:
- The walker starts on the dummy. The next values 1 and 2 are kept, so it advances to 1 and then to 2.
- On 2, the next value is 6, so the link is rewired to skip it:
2 -> 3. The walker stays on 2, and the new next value, 3, is a keeper. - The walker advances over 3, 4 and 5. On 5, the next value is 6, so 5 now points to
null. - Now
node.nextisnull, the loop ends, anddummy.nextis the node 1. The list is1 -> 2 -> 3 -> 4 -> 5.
Step by step
- Create the dummy node with
new ListNode()and store it in a variable declared withvar. It sits in front ofhead. - Link it to the list by setting
dummy.next = head. - Start the walker with
var node = dummy. It always stands on the node before the one being examined. - Loop while
node.next != null, which means there is still a candidate to look at. - Test the candidate: is
node.next.valequal toval? - If it matches, unlink it with
node.next = node.next.nextand do not advance. - Otherwise, in the
elsebranch, advance withnode = node.next. - Return
dummy.next, the head of the cleaned list.
The solution in C#
class Solution {
public ListNode Remove(ListNode head, int val) {
var dummy = new ListNode();
dummy.next = head;
var node = dummy;
while (node.next != null) {
if (node.next.val == val) {
node.next = node.next.next;
} else {
node = node.next;
}
}
return dummy.next;
}
}
C# details worth knowing
varinfers the type.var dummy = new ListNode();makesdummyaListNode, andvar node = dummy;does too. The variable is still statically typed, and the compiler fills in the name.- Parameter name
val. The method's parameter and the node's field are both calledval. They never clash, because the field is always reached through a node (node.next.val), while the bare namevalis the parameter. - Unlinked nodes are reclaimed. Once nothing refers to a removed node, the garbage collector frees it. No manual deletion is needed in C#.
- Reference semantics. The dummy's
valis the default 0, but it is never compared, because the loop only readsnode.next.val, and the dummy is never thenextof any node.
Complexity
Every pass through the loop either removes a node or advances past one, so each original node is handled exactly once and the loop runs at most n times: O(n) time overall. Only the dummy node and one walking reference are added, so the extra space is O(1).
Edge cases and common mistakes
- Removing the head. The dummy makes this ordinary. For
[7, 7, 7]withval = 7the result is empty:dummy.nextends upnull. - Consecutive matches. For
[1, 6, 6, 2]withval = 6, the result is[1, 2]. This only works because the walker stays put after a removal. - Advancing after every step. If you move
nodeforward even after unlinking, the second of two adjacent matches is skipped and stays in the list. - An empty list. A
nullhead is fine here: the dummy'snextisnull, the loop doesn't run, and the method returnsnull. - No matches. The loop just walks to the end and the original list comes back unchanged.
- Returning
head. If the first node was removed,headstill refers to the removed node. Returndummy.next. - Comparing nodes instead of values. Write
node.next.val == val. Comparingnode.next == valis a type error.
Testing
Results from running the solution:
[1, 2, 6, 3, 4, 5, 6]with 6 returns[1, 2, 3, 4, 5].[7, 7, 7]with 7 returns an empty list.[1, 6, 6, 2]with 6 returns[1, 2].