Delete Node in a Linked List
Description
You are given a singly-linked list and a reference to a specific node in that list. Your task is to delete that node from the list.
Here's the catch: you are not given access to the head of the linked list. You only receive the node that should be deleted.
By "deleting" the node, we mean:
- The value of the given node should no longer exist in the linked list.
- The number of nodes in the list should decrease by one.
- All values before the given node should remain in their original order.
- All values after the given node should remain in their original order.
All node values in the linked list are unique, and it is guaranteed that the given node is not the last (tail) node in the list.
Examples
Example 1
Input: head = [4, 5, 1, 9], node = 5
Output: [4, 1, 9]
Explanation: We are given a pointer to the node with value 5. After deletion, the linked list becomes 4 → 1 → 9. The node with value 5 no longer exists in the list, and the remaining nodes maintain their original order.
Example 2
Input: head = [4, 5, 1, 9], node = 1
Output: [4, 5, 9]
Explanation: We are given a pointer to the node with value 1. After deletion, the linked list becomes 4 → 5 → 9. The node with value 1 is removed while all other nodes remain in their original order.
Example 3
Input: head = [1, 2, 3, 4], node = 3
Output: [1, 2, 4]
Explanation: We are given a pointer to the node with value 3. After deletion, the linked list becomes 1 → 2 → 4. Notice that even though we never had access to the head node (1), the list is correctly modified.
Constraints
- The number of nodes in the 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
Editorial
Brute Force
Intuition
The typical way to delete a node from a singly linked list is to find the previous node and set its next pointer to skip over the target node. This requires traversing from the head of the list until we find the node just before the one we want to delete.
Imagine a chain of paperclips. To remove one from the middle, you would normally go back to the paperclip before it, unhook it, and re-hook it to the paperclip after the removed one. This requires access to the starting end of the chain.
However, in this problem, we do not have access to the head node. We only have a pointer directly to the node we need to delete. So the standard traversal approach is simply not possible here. We cannot find the previous node because we cannot traverse backwards in a singly linked list, and we cannot start from the head because we don't have it.
This means the brute force approach of 'find the previous node and re-link' is fundamentally blocked by the constraint of this problem.
Step-by-Step Explanation
Let's think through why the standard deletion approach fails:
Setup: head = [4, 5, 1, 9], node = 5 (we receive a pointer to the node with value 5).
Step 1: In a normal deletion, we would start at the head node (value 4) and traverse forward.
Step 2: We would find that node with value 4 has its next pointing to our target node (value 5).
Step 3: We would set node_4.next = node_5.next, effectively skipping over node 5.
Step 4: The list would become 4 → 1 → 9.
The Problem: Step 1 and Step 2 are impossible because we don't have access to the head node. We receive only a pointer to the node with value 5. From that node, we can only go forward (to 1, then 9), never backward (to 4).
Since a singly linked list has no backward pointers, there is no way to reach the previous node (value 4) from the given node (value 5). The standard approach is blocked.
Algorithm
Standard deletion (requires head access):
- Start at the head of the linked list
- Traverse until finding the node whose
nextpoints to the target node - Set
previous.next = target.nextto bypass the target - Delete/free the target node
This approach is NOT applicable to our problem because we lack access to the head.
Code
// Standard deletion — requires head access (NOT applicable here)
// Shown for learning purposes only
void deleteNode(ListNode* head, ListNode* target) {
// If head itself is the target
if (head == target) {
head = head->next;
return;
}
// Traverse to find previous node
ListNode* prev = head;
while (prev->next != nullptr && prev->next != target) {
prev = prev->next;
}
// Bypass the target
if (prev->next == target) {
prev->next = target->next;
}
}# Standard deletion — requires head access (NOT applicable here)
# Shown for learning purposes only
def delete_node(head, target):
# If head itself is the target
if head == target:
head = head.next
return head
# Traverse to find previous node
prev = head
while prev.next is not None and prev.next != target:
prev = prev.next
# Bypass the target
if prev.next == target:
prev.next = target.next
return head// Standard deletion — requires head access (NOT applicable here)
// Shown for learning purposes only
public void deleteNode(ListNode head, ListNode target) {
// If head itself is the target
if (head == target) {
head = head.next;
return;
}
// Traverse to find previous node
ListNode prev = head;
while (prev.next != null && prev.next != target) {
prev = prev.next;
}
// Bypass the target
if (prev.next == target) {
prev.next = target.next;
}
}Complexity Analysis
Time Complexity: O(n)
In the standard approach, we traverse from the head to find the predecessor of the target node. In the worst case, the target is near the end, requiring us to visit all n nodes.
Space Complexity: O(1)
Only a single pointer variable (prev) is used regardless of list size.
Limitation: This approach requires head access, which we do not have in this problem.
Why This Approach Is Not Efficient
The standard deletion approach isn't just inefficient — it's impossible in this problem. We simply don't have a pointer to the head of the list, so we cannot traverse backward to find the previous node.
But this limitation leads to a brilliant insight: what if we don't need to remove the node at all? Instead of physically removing the given node from the chain, what if we make it impersonate the next node?
The idea is: copy the data from the next node into the current node, and then delete the next node instead. From the outside, it looks exactly as if the given node was deleted — the value disappears, the list shrinks by one, and the order is preserved. This is like a spy taking on a new identity: instead of extracting the spy, you change who the spy pretends to be.
Optimal Approach - Copy and Skip
Intuition
Since we cannot reach the previous node, we use a clever trick: instead of removing the given node, we overwrite it with the next node's data and then remove the next node.
Think of it like a relay race. If runner #3 needs to be removed from the team but you can only communicate with runners #3, #4, and beyond (not runners #1 and #2), you tell runner #3 to take on runner #4's number and baton, and then runner #4 quietly leaves. To the audience, it looks like runner #3 disappeared — but actually, runner #3 is now playing the role of runner #4.
Concretely:
- Copy
node.next.valintonode.val— the current node now holds the next node's value. - Set
node.next = node.next.next— the current node now points past the next node, effectively removing the next node from the chain.
The result: the value that was in the given node is gone from the list, the list is one node shorter, and all other values remain in order. This satisfies every requirement of the problem.
Step-by-Step Explanation
Let's trace through with head = [4, 5, 1, 9], node = 5:
Step 1: We receive a pointer to the node with value 5. The list looks like:
4 → [5] → 1 → 9 → null
The brackets indicate the node we have a pointer to.
Step 2: Look at the next node's value: node.next.val = 1.
Step 3: Copy the next node's value into the current node: node.val = 1.
Now the list looks like: 4 → [1] → 1 → 9 → null.
Notice there are temporarily two nodes with value 1.
Step 4: Save a reference to node.next (the original node with value 1).
Step 5: Bypass the next node: node.next = node.next.next.
The current node now points to the node with value 9.
The list becomes: 4 → [1] → 9 → null.
Step 6: The original next node (which had value 1) is now disconnected and can be garbage collected.
Result: The list is 4 → 1 → 9. The value 5 no longer exists. The list shrank by one node. All done in O(1) time!
Copy-and-Skip Deletion — Impersonate the Next Node — Watch how the given node copies the next node's value, then skips over the next node. The effect is the same as if the given node was removed.
Algorithm
- Copy the value of
node.nextintonode:node.val = node.next.val
- Skip over the next node by updating the pointer:
node.next = node.next.next
- The next node is now orphaned and will be garbage collected
That's it — just two lines of code. The simplicity is the beauty of this approach.
Code
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
// Copy the next node's value into the current node
node->val = node->next->val;
// Skip the next node
node->next = node->next->next;
}
};# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
# Copy the next node's value into the current node
node.val = node.next.val
# Skip the next node
node.next = node.next.next/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public void deleteNode(ListNode node) {
// Copy the next node's value into the current node
node.val = node.next.val;
// Skip the next node
node.next = node.next.next;
}
}Complexity Analysis
Time Complexity: O(1)
We perform exactly two operations — one value copy and one pointer reassignment. No traversal, no loops, no searching. Constant time regardless of list size.
Space Complexity: O(1)
No extra data structures are used. We only modify existing pointers and values in place.
This is optimal in both time and space. The O(1) time is a dramatic improvement over the O(n) traversal approach, made possible by the creative insight of copying data instead of rearranging the structure.
Caveats worth noting:
- This approach does NOT work if the node to be deleted is the last node (tail), because there is no next node to copy from. The problem guarantees the node is not the tail.
- This approach modifies the value of the given node, not just pointers. If external code holds a reference to the node and expects its value to remain the same, this could cause issues. For this LeetCode problem, that is acceptable.