Facebook Pixel

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 time
  • fast 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 Evaluator

Example 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 → 1
  • fast 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 → 2
  • fast 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm is best for finding the shortest distance between two points in an unweighted graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More