Problem overview
The function remove receives the head of a singly linked list and an integer value. It removes every node whose value equals value and returns the head of the resulting list. For the list [1, 2, 6, 3, 4, 5, 6] and the value 6, the result is [1, 2, 3, 4, 5].
The statement asks for O(n) time and O(1) extra space, so the function must relink the existing nodes in a single pass.
Approach: unlink from the node before
To remove a node from a singly linked list, you do not touch the node itself. You change the next of the node before it, so that it skips over the unwanted node. This means the loop must stand on the node before the one it examines, and look one step ahead.
That raises a problem for the head. The first node has no node before it, so removing it would need its own special case, and a list in which the first several nodes all match makes that case loop. A dummy node removes the problem: a placeholder node placed in front of the head. The real head now has a predecessor, so every node is removed the same way, and the answer is whatever follows the dummy at the end.
One rule keeps the loop correct: after you unlink a node, do not move forward. The node that now follows may match as well. Move forward only when the next node is kept.
On [6, 6, 1] with the value 6: the dummy's next is a 6, so it is unlinked; the new next is also a 6, so it is unlinked too; then the next is 1, which stays, and the walker moves onto it. The result is [1].
Step by step
- Create the dummy. Store a new empty
ListNode()in avalnameddummy. - Link it to the list by setting
dummy.nexttohead. - Declare the walker.
nodeis avarof typeListNode?, starting atdummy. It is nullable because later it is assignednode.next, which can be null. It always stands on the node before the one being examined. - Loop while there is a next node:
node!!.next != null. The!!says thatnodeitself is never null here, which holds because the walker only moves onto a non-null next node. - Compare the next node's value. Check whether
node.next!!.`val`equalsvalue. The field is namedval, a Kotlin keyword, so it needs backticks. - On a match, unlink it: set
node.nexttonode.next!!.next. The walker stays where it is. - Otherwise move on: set
nodetonode.next. - Return
dummy.next, the head of the cleaned list. It may be different from the originalhead, ornullif every node was removed.
The solution in Kotlin
class Solution {
fun remove(head: ListNode?, value: Int): ListNode? {
val dummy = ListNode()
dummy.next = head
var node: ListNode? = dummy
while (node!!.next != null) {
if (node.next!!.`val` == value) {
node.next = node.next!!.next
} else {
node = node.next
}
}
return dummy.next
}
}
The Kotlin details worth a closer look:
- The explicit type
ListNode?. Without it, Kotlin would inferListNodefromdummy, andnode = node.nextwould not compile, sincenode.nextmay be null. The annotation widens the variable's type. - Why
!!appears twice.node!!in the loop condition asserts the walker is not null, and Kotlin then treats it as non-null for the rest of the body.node.nextis a mutable property, so the compiler never smart-casts it, andnode.next!!is needed wherever the code uses it as a node. The loop condition has already shown that it is not null. - Backticks on
val. The node's field is namedval, a Kotlin keyword, so it is written with backticks:node.next!!.`val`. The function's parameter is calledvalue, which needs no such care. - Empty nodes.
ListNode()works because the node's value has a default. The dummy's own value is never read. - Removed nodes. Once nothing refers to an unlinked node, the JVM's garbage collector reclaims it; there is nothing to free by hand.
Complexity
Time: O(n). Every pass of the loop either unlinks a node or moves the walker one node forward, and each node is handled once. The total number of passes is at most the number of nodes.
Space: O(1). The function creates one dummy node and two references, regardless of the list size.
Edge cases and common mistakes
- An empty list. With
head = null, the dummy's next is null, the loop does not run, and the function returnsnull. - Every node matches.
[7, 7, 7]with the value 7 gives an empty list, sincedummy.nextends upnull. - The head matches.
[6, 1]with the value 6 returns[1]. This is where the dummy earns its place. - Consecutive matches.
[1, 6, 6, 2]with the value 6 must give[1, 2]. Moving the walker after an unlink would skip the second 6. - No match. The list comes back unchanged.
- Returning
head. If the first node was removed,headstill points to it, and the result would be wrong. Returndummy.next. - Standing on the node being checked. A walker that stands on the matching node cannot unlink it, because only the node before it holds the link to change.
Testing
[1, 2, 6, 3, 4, 5, 6]with value 6 gives[1, 2, 3, 4, 5].[7, 7, 7]with value 7 gives[].[]with value 1 gives[].[6, 1]with value 6 gives[1].[1, 6, 6, 2]with value 6 gives[1, 2].