Problem overview
The method remove receives the head of a singly linked list and an integer value. It removes every node whose val equals value and returns the head of the new list. For the list 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6 and value = 6, the result is 1 -> 2 -> 3 -> 4 -> 5.
The statement asks for O(n) time and O(1) extra space, so the method changes the links of the existing list in one pass and builds no copy.
Approach: unlink matches from the node before them
Removing a node from a singly linked list means changing the next of the node before it, so that the link skips the removed node. A node can't remove itself, because it has no reference to its predecessor. The loop therefore stands on the node before the one it examines, and looks one step ahead with node.next.
The head is the awkward case. It has no predecessor, and it may need to be removed, even several times in a row (6 -> 6 -> 1). Handling that separately needs extra code. A dummy node removes the problem: create a throwaway node and link it in front of the head. Now every real node, including the head, has a node before it, and the loop treats all of them the same way. At the end, dummy.next is the head of the cleaned list, whether or not the original head was removed.
The one subtle rule: after unlinking a node, do not advance. The node that moved into node.next may also match, as with the two 6s in 6 -> 6 -> 1. Only when node.next is kept does node move forward.
Step by step
- Create
dummyas a new emptyListNode(), declared withval. It sits in front ofhead. - Link it: set
dummy.nexttohead. - Start
nodeatdummy, declared withvar. It always stands on the node before the one being examined. - Loop while
node.next != null, that is, while there is still a node to examine. - Compare the next node's value,
node.next.`val`, with the parametervalue. Becausevalis a Scala keyword, the field name is written in backticks. - On a match, unlink it with
node.next = node.next.next, and leavenodewhere it is. - Otherwise move on: in the
elsebranch, setnode = node.next. - Return
dummy.next, the head of the cleaned list.
The solution in Scala
class Solution {
def remove(head: ListNode, value: Int): ListNode = {
val dummy = ListNode()
dummy.next = head
var node = dummy
while (node.next != null) {
if (node.next.`val` == value) {
node.next = node.next.next
} else {
node = node.next
}
}
dummy.next
}
}
The Scala details worth a closer look:
- Backticks for
val.node.next.`val`reads the field namedval. Without backticks the compiler reads the keyword and reports a syntax error. The parameter is calledvalueto avoid the same clash. val dummyandvar node. The dummy node reference never changes, so it is aval, even though itsnextfield changes.nodemoves along the list, so it is avar.ListNode()withoutnew. Scala 3 allows calling a class like a function, and the node class has default values for its fields, so no arguments are needed.- Everything is a reference. The assignment
node = dummydoesn't copy a node; it makesnoderefer to the same object. Changingnode.nextchanges the node thatdummyrefers to, too, as long as the two refer to the same node. - The last expression is the result.
dummy.nextneeds noreturn. - Null checks are safe here. Inside the loop body
node.nextisn'tnull, sonode.next.`val`is safe. When the last node is unlinked,node.next.nextisnull, which correctly ends the list.
Complexity
Time: O(n). Each iteration either removes a node or moves node forward, and every node is examined once.
Space: O(1). The method creates one dummy node and one extra reference, however long the list is. Removed nodes are not copied; once nothing refers to them, the JVM can reclaim them.
Edge cases and common mistakes
- The head matches. For 6 -> 6 -> 1 with
value = 6, the loop unlinks both 6s from the dummy and returns 1. Without a dummy node, this case needs its own loop. - Everything matches. For 7 -> 7 -> 7 and
value = 7, the result is an empty list:dummy.nextisnull. - An empty list. A
nullhead givesdummy.next == null, the loop doesn't run, and the method returnsnull. - Nothing matches. The list is returned unchanged, because every iteration takes the
elsebranch. - Consecutive matches. For 1 -> 2 -> 2 -> 1 with
value = 2, the result is 1 -> 1. Ifnodemoved forward after the first removal, the second 2 would be skipped over and stay in the list. - Returning
head. If the original head was removed,headstill points at a node that is no longer in the list. Always returndummy.next. - Standing on the examined node. If
nodewere the node being checked, there would be no way to relink its predecessor.
Testing
- 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6 with
value = 6gives 1 -> 2 -> 3 -> 4 -> 5. - 7 -> 7 -> 7 with
value = 7gives an empty list. - 6 -> 6 -> 1 with
value = 6gives 1. - 1 -> 2 -> 2 -> 1 with
value = 2gives 1 -> 1. - 1 -> 2 with
value = 5gives 1 -> 2. - An empty list gives an empty list.
