Problem overview
The function remove receives the head of a singly linked list of *ListNode values and an integer val. It must delete every node whose Val equals val and return the head of the resulting list. For [1, 2, 6, 3, 4, 5, 6] and val = 6 the answer is [1, 2, 3, 4, 5].
The statement asks for O(n) time and O(1) extra space, so the list has to be edited in place, with a single pass and no copy.
Approach: stand one node behind the one you test
In a singly linked list, deleting a node means changing the Next of the node before it so that it skips the victim: before.Next = before.Next.Next. That has two consequences.
First, the walking pointer must stand on the node before the one being examined. It reads node.Next.Val to decide, and edits node.Next to delete.
Second, the head has no node before it. If the head itself has to go, a plain walk needs a special case that moves head forward, possibly several times. A dummy node removes the special case: create an empty node in front of the list, so that every real node, the head included, has a predecessor. At the end, dummy.Next is the head of the cleaned list, whether or not the original head survived.
One more detail matters. After deleting a node, the walking pointer must stay where it is. The node that took the deleted one's place may also match. In [6, 6], advancing after the first removal would step over the second 6.
Here is the walk on [1, 6, 6, 2] with val = 6. The list is written with the dummy in front, and the pointer node is named for the node it stands on.
nodeis the dummy andnode.Nextis 1. It isn't 6, sonodemoves to 1.node.Nextis the first 6. It matches, so 1 is linked straight to the second 6. The list is now dummy, 1, 6, 2 andnodestays on 1.node.Nextis the remaining 6. It matches, so 1 is linked to 2.node.Nextis 2. It doesn't match, sonodemoves to 2. Thennode.Nextisniland the loop ends.
The result is dummy.Next, the list [1, 2].
Step by step
- Create the dummy.
&ListNode{}builds a new node with zero values and gives back a pointer to it, whichdummystores. - Link it to the list:
dummy.Next = head. The dummy now sits in front of the first real node. - Start
nodeat the dummy. It always stands on the node before the one under inspection. - Loop while
node.Next != nil. This is Go's way of writing awhile: aforwith just a condition. It stops when there is no node left to inspect. - Compare
node.Next.Valwithval. - On a match, unlink:
node.Next = node.Next.Next. Do not advance, because the newnode.Nexthasn't been checked yet. - Otherwise, advance:
node = node.Next. Only a node that is kept becomes the new "node before". - Return
dummy.Next, which is the real head after all removals.
The solution in Go
func remove(head *ListNode, val int) *ListNode {
dummy := &ListNode{}
dummy.Next = head
node := dummy
for node.Next != nil {
if node.Next.Val == val {
node.Next = node.Next.Next
} else {
node = node.Next
}
}
return dummy.Next
}
Go details worth knowing:
- Composite literals and
&.ListNode{}is a struct value with every field at its zero value:Valis 0 andNextisnil. Putting&in front gives a*ListNode. The dummy'sValis never read, so its 0 doesn't matter. - Dot syntax through pointers.
node.Next.Nextchains field accesses on pointers with no special operator. If an intermediate pointer isnil, the program panics with a nil pointer dereference. Herenode.Next != nilis checked by the loop condition before the body readsnode.Next.Val. - Memory. Unlinked nodes are not freed by hand. Once nothing points to them, the garbage collector reclaims them.
Complexity
Time: O(n). Every iteration either removes a node or moves node one step forward, so the loop runs at most n times, with constant work in each.
Space: O(1). The only extra memory is the dummy node and the node pointer.
Edge cases and common mistakes
- Removing the head. Because the dummy precedes the head, deleting it is the same assignment as deleting any other node.
[6, 1]withval = 6returns[1]. - Consecutive matches.
[1, 2, 6, 6, 3]withval = 6returns[1, 2, 3]. A version that deletes on a match and then always executesnode = node.Nextmoves onto the second 6 instead of checking it, so it leaves that 6 in place and returns[1, 2, 6, 3]. - Every node matches.
[7, 7, 7]withval = 7leaves only the dummy, sodummy.Nextisniland the function returns an empty list. - Empty list. With
head == nil,dummy.Nextisnil, the loop doesn't run and the function returnsnil. - Returning
headinstead ofdummy.Next. If the original head was removed,headstill points at a node that is no longer in the list. - No match at all. Every node is kept, and the returned list is the original one, untouched.
Testing
[1, 2, 6, 3, 4, 5, 6]withval = 6gives[1, 2, 3, 4, 5].[7, 7, 7]withval = 7gives[].[1, 2, 6, 6, 3]withval = 6gives[1, 2, 3].[]with anyvalgives[].