Delete Node in a Linked List
Delete Node in a Linked List: you are given only the node to delete (never the tail), with no access to the head. Delete it.
- The number of the nodes in the given list is in the range [2, 1000].
- -1000 <= Node.val <= 1000
- The value of each node in the list is unique.
- The node to be deleted is in the list and is not a tail node.
Intuition
Delete node in a linked list is unusual: you are given only the node to delete, with no access to the head and no way to reach the previous node. The standard deletion — relinking the predecessor past the target — is simply unavailable.
Since the previous node cannot be found, the node itself cannot truly be removed from the chain. The trick is to change what "deleting" means:
- Copy the next node's value into this node, then delete the next node instead.
The node object stays in the list, but the value it held is gone and every value after it shifts back one position. From the outside, the list reads exactly as though the target had been removed — which is what the problem actually checks.
Concretely: node.val = node.next.val, then node.next = node.next.next. The second line unlinks the successor, whose value now lives in the current node.
The reason this is guaranteed to work is stated in the constraints — the node to delete is never the tail. Without that guarantee the approach would fail, since a tail node has no successor to copy from and no way to reach its predecessor. Recognising that the constraint is what makes the trick legal is the real insight.
In languages with manual memory management, the orphaned successor node should be freed after unlinking.
The genuinely instructive part is realising that when the usual mechanism is unavailable, redefining the operation in terms of values rather than structure solves it.
Delete node in a linked list without head: a puzzle about reframing, since you're given only the node to delete, with no access to its predecessor, so you cannot unlink it in the usual way. The move is to stop thinking about deleting this node and instead overwrite it with its successor's data, then unlink the successor. The node object survives; the value disappears, which is all anyone can observe.
Approach
Before reading on: price up what the direct approach costs here, then ask which pointers must be saved before you overwrite anything. Aim for O(1) time and O(1) space.
See why normal deletion fails
Removing a node requires relinking its predecessor, but the head is not given and the previous node cannot be reached. The standard technique is unavailable.
Redefine the operation
The node object cannot leave the chain, but its value can. Copy the next node's value into this one, and the list now reads as if the target were gone.
Copy the successor's value
Set node.val = node.next.val. The target's original value is overwritten and every later value effectively shifts back one position.
Unlink the successor
Set node.next = node.next.next, removing the node whose value was just copied. The observable list is now exactly the intended result.
Rely on the tail guarantee
The constraints promise the node is never the tail. A tail node has no successor to copy from and no reachable predecessor — that guarantee is what makes this approach legal.
Cost of the operation
Two assignments and no traversal give O(1) time and O(1) space — the entire point of the value-copying trick.
Solution & live demo
Common pitfalls
Trying to unlink the node itself
node.prev.next = node.next
node.val = node.next.val node.next = node.next.next
It's a singly linked list and you were handed no head pointer, so the predecessor is unreachable. Impersonating the next node achieves the same observable result without it.
Copying the value but not relinking
node.val = node.next.val
node.val = node.next.val node.next = node.next.next
The value now appears twice in a row and the list keeps its original length. Both steps are needed: take over the successor's identity, then remove the successor.
Assuming it works for the tail
# called on the last node
# guaranteed by the problem: node is never the tail
There is no successor to impersonate, and node.next.val raises. The technique fundamentally cannot delete a tail node — which is why the constraints rule that case out.
Edge cases
Its successor's value is copied in and the successor is skipped — the list reads identically minus the target value.
Relies on node.next existing; the problem promises the node is not last, so this is safe.
Only the given node is altered; other nodes with the same value are untouched.