2095. Delete the Middle Node of a Linked List
Problem Description
You are given the head of a linked list. Your task is to delete the middle node and return the head of the modified linked list.
The middle node is defined using 0-based indexing as the node at position ⌊n/2⌋
, where n
is the total number of nodes in the linked list and ⌊x⌋
represents the floor function (largest integer less than or equal to x
).
For example:
- If the list has 1 node (n=1): the middle node is at index 0 (the only node)
- If the list has 2 nodes (n=2): the middle node is at index 1 (the second node)
- If the list has 3 nodes (n=3): the middle node is at index 1 (the second node)
- If the list has 4 nodes (n=4): the middle node is at index 2 (the third node)
- If the list has 5 nodes (n=5): the middle node is at index 2 (the third node)
The solution uses the fast and slow pointer technique. A dummy node is created that points to the head, with slow
starting at the dummy node and fast
starting at the head. The fast
pointer moves twice as fast as the slow
pointer. When fast
reaches the end of the list, slow
will be positioned right before the middle node. The middle node is then deleted by updating slow.next
to skip over it (slow.next = slow.next.next
). Finally, dummy.next
is returned as the new head of the modified list.
Intuition
The key insight is that we need to find and remove the middle node in a single pass through the linked list. Since we can't directly access nodes by index in a linked list, we need a clever way to locate the middle position.
The fast and slow pointer technique naturally solves this problem. If we have two pointers where one moves twice as fast as the other, when the fast pointer reaches the end, the slow pointer will be at the halfway point. Think of it like two runners on a track - if one runs at double the speed, when the faster runner completes the full lap, the slower runner will have completed exactly half a lap.
However, there's a subtle challenge: we don't just need to find the middle node, we need to delete it. To delete a node in a linked list, we need access to the node before it so we can update its next
pointer to skip the middle node.
This is why the solution cleverly starts the slow
pointer one step behind by placing it at a dummy node that points to the head, while fast
starts at the actual head. This offset ensures that when fast
reaches the end, slow
is positioned exactly one node before the middle node - the perfect position to perform the deletion.
The beauty of this approach is that it handles all edge cases naturally. Whether the list has an odd or even number of nodes, the ⌊n/2⌋
calculation is implicitly handled by the pointer movement pattern. The dummy node also elegantly handles the case where we might need to delete the first node of the list.
Learn more about Linked List and Two Pointers patterns.
Solution Approach
The implementation uses the fast and slow pointer technique with a clever initialization to position the pointers correctly for deletion.
Step 1: Initialize the pointers
dummy = ListNode(next=head)
slow, fast = dummy, head
We create a dummy node that points to the head of the list. The slow
pointer starts at the dummy node (one position before the head), while the fast
pointer starts at the head. This offset is crucial - it ensures that when we find the middle, slow
will be positioned right before it.
Step 2: Move the pointers
while fast and fast.next:
slow = slow.next
fast = fast.next.next
We move both pointers through the list:
slow
moves one step at a timefast
moves two steps at a time
The loop continues while fast
is not null and fast.next
is not null. This condition handles both odd and even length lists:
- For even-length lists:
fast
will become null - For odd-length lists:
fast.next
will become null
When this loop ends, slow
is positioned exactly one node before the middle node we want to delete.
Step 3: Delete the middle node
slow.next = slow.next.next
Since slow
is positioned before the middle node, slow.next
is the middle node itself. We skip over it by setting slow.next
to point to slow.next.next
, effectively removing the middle node from the list.
Step 4: Return the modified list
return dummy.next
We return dummy.next
, which is the head of the modified list. Using the dummy node ensures this works even if the original list had only one node (in which case we'd be deleting the head itself, and dummy.next
would now be null).
The time complexity is O(n)
as we traverse the list once, and the space complexity is O(1)
as we only use a constant amount of extra space for the pointers.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with the linked list: 1 -> 2 -> 3 -> 4 -> 5
We need to delete the middle node. With n=5 nodes, the middle node is at index ⌊5/2⌋ = 2, which is the node with value 3.
Initial Setup:
dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> null ↑ ↑ slow fast
We create a dummy node pointing to head (1). The slow
pointer starts at dummy, and fast
starts at the head (1).
First Iteration:
dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> null ↑ ↑ slow fast
slow
moves one step: dummy → 1fast
moves two steps: 1 → 2 → 3- Condition check: fast (3) exists and fast.next (4) exists ✓
Second Iteration:
dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> null ↑ ↑ slow fast
slow
moves one step: 1 → 2fast
moves two steps: 3 → 4 → 5- Condition check: fast (5) exists but fast.next is null ✗
Loop Ends - Delete the Middle Node:
Now slow
is at node 2, which is exactly one position before the middle node (3).
Before deletion: dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> null ↑ slow slow.next = slow.next.next (skip node 3) After deletion: dummy -> 1 -> 2 --------> 4 -> 5 -> null ↑ slow
Return Result:
Return dummy.next
, which gives us the head of the modified list: 1 -> 2 -> 4 -> 5
The middle node (3) has been successfully removed from the list.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7from typing import Optional
8
9class Solution:
10 def deleteMiddle(self, head: Optional[ListNode]) -> Optional[ListNode]:
11 """
12 Delete the middle node of a linked list.
13 For even-length lists, delete the second middle node.
14
15 Args:
16 head: The head of the linked list
17
18 Returns:
19 The head of the modified linked list
20 """
21 # Create a dummy node pointing to head to handle edge cases
22 dummy_node = ListNode(next=head)
23
24 # Initialize two pointers for the two-pointer technique
25 # slow_ptr will eventually point to the node before the middle
26 slow_ptr = dummy_node
27 fast_ptr = head
28
29 # Move fast_ptr twice as fast as slow_ptr
30 # When fast_ptr reaches the end, slow_ptr will be before the middle
31 while fast_ptr and fast_ptr.next:
32 slow_ptr = slow_ptr.next
33 fast_ptr = fast_ptr.next.next
34
35 # Delete the middle node by skipping it
36 slow_ptr.next = slow_ptr.next.next
37
38 # Return the head of the modified list
39 return dummy_node.next
40
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 public ListNode deleteMiddle(ListNode head) {
13 // Create a dummy node pointing to head to handle edge cases
14 // This helps when we need to delete the first node
15 ListNode dummyNode = new ListNode(0, head);
16
17 // Initialize two pointers for the two-pointer technique
18 // slowPointer will eventually point to the node before the middle
19 ListNode slowPointer = dummyNode;
20 // fastPointer moves twice as fast to find the middle
21 ListNode fastPointer = head;
22
23 // Traverse the list using two-pointer technique
24 // When fastPointer reaches the end, slowPointer will be just before the middle
25 while (fastPointer != null && fastPointer.next != null) {
26 slowPointer = slowPointer.next;
27 fastPointer = fastPointer.next.next;
28 }
29
30 // Delete the middle node by skipping it
31 // Connect the node before middle to the node after middle
32 slowPointer.next = slowPointer.next.next;
33
34 // Return the head of the modified list
35 return dummyNode.next;
36 }
37}
38
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 ListNode* deleteMiddle(ListNode* head) {
14 // Create a dummy node pointing to head to handle edge cases
15 // (e.g., when the list has only one or two nodes)
16 ListNode* dummyNode = new ListNode(0, head);
17
18 // Initialize two pointers for the two-pointer technique
19 // slowPtr will eventually point to the node before the middle node
20 ListNode* slowPtr = dummyNode;
21 // fastPtr moves twice as fast as slowPtr
22 ListNode* fastPtr = head;
23
24 // Move fastPtr two steps and slowPtr one step at a time
25 // When fastPtr reaches the end, slowPtr will be just before the middle
26 while (fastPtr != nullptr && fastPtr->next != nullptr) {
27 slowPtr = slowPtr->next;
28 fastPtr = fastPtr->next->next;
29 }
30
31 // Delete the middle node by skipping it
32 // slowPtr->next is the middle node to be deleted
33 slowPtr->next = slowPtr->next->next;
34
35 // Return the head of the modified list
36 // dummyNode->next handles the case where the original head was deleted
37 return dummyNode->next;
38 }
39};
40
1/**
2 * Definition for singly-linked list node
3 */
4class ListNode {
5 val: number;
6 next: ListNode | null;
7
8 constructor(val?: number, next?: ListNode | null) {
9 this.val = (val === undefined ? 0 : val);
10 this.next = (next === undefined ? null : next);
11 }
12}
13
14/**
15 * Deletes the middle node from a singly-linked list
16 * If the list has an even number of nodes, deletes the second middle node
17 *
18 * @param head - The head of the linked list
19 * @returns The head of the modified linked list with middle node removed
20 *
21 * Time Complexity: O(n) where n is the number of nodes
22 * Space Complexity: O(1) - only using pointers
23 *
24 * Algorithm:
25 * 1. Use two-pointer technique (slow and fast pointers)
26 * 2. Fast pointer moves twice as fast as slow pointer
27 * 3. When fast reaches the end, slow will be just before the middle node
28 * 4. Skip the middle node by adjusting the next pointer
29 */
30function deleteMiddle(head: ListNode | null): ListNode | null {
31 // Create dummy node to handle edge cases (single node list)
32 const dummyNode: ListNode = new ListNode(0, head);
33
34 // Initialize two pointers
35 // slow starts at dummy, fast starts at head
36 let slowPointer: ListNode = dummyNode;
37 let fastPointer: ListNode | null = head;
38
39 // Move fast pointer twice as fast as slow pointer
40 // When fast reaches end, slow will be at node before middle
41 while (fastPointer !== null && fastPointer.next !== null) {
42 slowPointer = slowPointer.next!;
43 fastPointer = fastPointer.next.next;
44 }
45
46 // Remove the middle node by skipping it
47 slowPointer.next = slowPointer.next!.next;
48
49 // Return the head of modified list (dummy.next)
50 return dummyNode.next;
51}
52
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the linked list. This is because the algorithm uses the two-pointer technique (slow and fast pointers) to traverse the list. The fast pointer moves twice as fast as the slow pointer, reaching the end of the list after approximately n/2
iterations. Since the slow pointer also moves n/2
times, the total number of operations is proportional to n
.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space regardless of the input size. It creates a dummy node and uses two pointer variables (slow and fast), which require fixed memory allocation that doesn't scale with the size of the input linked list.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Pointer Initialization
A common mistake is initializing both slow
and fast
pointers at the same starting position (both at head
or both at dummy
). This leads to incorrect positioning when the loop ends.
Incorrect approach:
# Wrong: Both pointers start at head
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Now slow points to the middle node, not the node before it!
Why this fails: When both pointers start at the same position, slow
ends up pointing to the middle node itself rather than the node before it. You then cannot delete the middle node properly since you need access to the previous node to update its next
pointer.
Solution: Always maintain the offset by starting slow
at dummy
and fast
at head
:
dummy = ListNode(next=head)
slow = dummy # Starts one position behind
fast = head # Starts at head
2. Not Handling Single-Node Lists
Without a dummy node, deleting the only node in a single-node list becomes problematic.
Incorrect approach:
# Wrong: No dummy node
if not head or not head.next:
return None # Special case handling
slow = head
fast = head.next
# ... rest of the logic
Why this fails: This requires special case handling for single-node lists, making the code more complex and error-prone. You need to remember to check for this edge case explicitly.
Solution: Using a dummy node elegantly handles all cases including single-node lists:
dummy = ListNode(next=head)
# No special cases needed - the algorithm handles n=1 naturally
3. Incorrect Loop Condition
Using only while fast.next and fast.next.next
can cause issues.
Incorrect approach:
# Wrong: Checking fast.next.next in the condition
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
Why this fails: This condition stops the loop too early for certain list lengths. For example, with a 2-node list, the loop wouldn't execute at all, leaving slow
at the dummy node when it should move once.
Solution: Use the simpler and correct condition:
while fast and fast.next:
slow = slow.next
fast = fast.next.next
This ensures the loop runs the correct number of times for all list lengths.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!