876. Middle of the Linked List

Problem Description

You are given the head of a singly linked list. Your task is to find and return the middle node of this linked list.

The middle node is defined as follows:

  • If the linked list has an odd number of nodes, return the node at the exact center position
  • If the linked list has an even number of nodes, there will be two middle nodes - return the second middle node

For example:

  • If the linked list is 1 -> 2 -> 3 -> 4 -> 5, the middle node is 3 (the third node)
  • If the linked list is 1 -> 2 -> 3 -> 4 -> 5 -> 6, there are two middle nodes (3 and 4), so you should return 4 (the second middle node)

The solution uses the two-pointer technique (also known as the tortoise and hare algorithm):

  • Initialize two pointers, slow and fast, both starting at the head
  • Move slow one step at a time (slow = slow.next)
  • Move fast two steps at a time (fast = fast.next.next)
  • Continue this process while fast and fast.next are not None
  • When fast reaches the end, slow will be at the middle node

This works because fast moves twice as fast as slow. When fast reaches the end of the list, slow will have traveled exactly half the distance, positioning it at the middle node. For even-length lists, this naturally gives us the second middle node as required.

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

Intuition

The key insight comes from thinking about the relationship between distance and speed. Imagine two runners on a track - one runs twice as fast as the other. When the faster runner completes the full track, where would the slower runner be? Exactly at the halfway point!

This same principle applies to traversing a linked list. If we have two pointers moving through the list at different speeds - one moving one node at a time and another moving two nodes at a time - when the faster pointer reaches the end, the slower pointer will be at the middle.

Why does this work mathematically? Let's say the linked list has n nodes:

  • The slow pointer moves 1 step per iteration
  • The fast pointer moves 2 steps per iteration
  • When fast has traversed n nodes (reaching the end), it took n/2 iterations
  • In those same n/2 iterations, slow moved n/2 nodes, placing it exactly at the middle

For odd-length lists (like 5 nodes), slow ends up at position ⌊5/2⌋ + 1 = 3 (the exact middle). For even-length lists (like 6 nodes), slow ends up at position 6/2 = 3, but since we count from position 0, this gives us the 4th node, which is the second middle node - exactly what we want!

The beauty of this approach is that we don't need to know the length of the list beforehand. We don't need to traverse it once to count nodes and then traverse again to find the middle. Everything happens in a single pass, making this an elegant O(n) time solution with O(1) space complexity.

Learn more about Linked List and Two Pointers patterns.

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 7class Solution: 8 def middleNode(self, head: ListNode) -> ListNode: 9 """ 10 Find the middle node of a linked list using the two-pointer technique. 11 12 Args: 13 head: The head node of the linked list 14 15 Returns: 16 The middle node of the linked list. If the list has an even number 17 of nodes, returns the second middle node. 18 """ 19 # Initialize both pointers to start at the head 20 slow_pointer = head 21 fast_pointer = head 22 23 # Move fast pointer twice as fast as slow pointer 24 # When fast pointer reaches the end, slow pointer will be at the middle 25 while fast_pointer and fast_pointer.next: 26 # Move slow pointer one step forward 27 slow_pointer = slow_pointer.next 28 # Move fast pointer two steps forward 29 fast_pointer = fast_pointer.next.next 30 31 # Return the middle node (where slow pointer ended up) 32 return slow_pointer 33
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 /** 13 * Finds the middle node of a linked list. 14 * If the list has an even number of nodes, returns the second middle node. 15 * Uses the two-pointer technique (slow and fast pointers). 16 * 17 * @param head The head of the linked list 18 * @return The middle node of the linked list 19 */ 20 public ListNode middleNode(ListNode head) { 21 // Initialize two pointers at the head of the list 22 // slowPointer moves one step at a time 23 // fastPointer moves two steps at a time 24 ListNode slowPointer = head; 25 ListNode fastPointer = head; 26 27 // Traverse the list until fastPointer reaches the end 28 // When fastPointer reaches the end, slowPointer will be at the middle 29 while (fastPointer != null && fastPointer.next != null) { 30 // Move slowPointer one step forward 31 slowPointer = slowPointer.next; 32 // Move fastPointer two steps forward 33 fastPointer = fastPointer.next.next; 34 } 35 36 // Return the middle node (where slowPointer is positioned) 37 return slowPointer; 38 } 39} 40
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 /** 14 * Find the middle node of a linked list. 15 * Uses the two-pointer technique (slow and fast pointers). 16 * 17 * @param head The head of the linked list 18 * @return The middle node of the linked list 19 * If the list has an even number of nodes, returns the second middle node 20 */ 21 ListNode* middleNode(ListNode* head) { 22 // Initialize two pointers at the head of the list 23 ListNode* slowPointer = head; 24 ListNode* fastPointer = head; 25 26 // Move fast pointer twice as fast as slow pointer 27 // When fast pointer reaches the end, slow pointer will be at the middle 28 while (fastPointer != nullptr && fastPointer->next != nullptr) { 29 // Move slow pointer one step forward 30 slowPointer = slowPointer->next; 31 32 // Move fast pointer two steps forward 33 fastPointer = fastPointer->next->next; 34 } 35 36 // Return the middle node (pointed by slow pointer) 37 return slowPointer; 38 } 39}; 40
1/** 2 * Definition for singly-linked list node 3 */ 4interface ListNode { 5 val: number; 6 next: ListNode | null; 7} 8 9/** 10 * Finds the middle node of a linked list using the two-pointer technique 11 * 12 * @param head - The head node of the linked list 13 * @returns The middle node of the linked list, or the second middle node if the list has an even number of nodes 14 * 15 * Time Complexity: O(n) where n is the number of nodes in the linked list 16 * Space Complexity: O(1) as we only use two pointers 17 * 18 * Algorithm: 19 * - Uses fast and slow pointers (Floyd's Tortoise and Hare algorithm) 20 * - Fast pointer moves two steps at a time 21 * - Slow pointer moves one step at a time 22 * - When fast pointer reaches the end, slow pointer will be at the middle 23 */ 24function middleNode(head: ListNode | null): ListNode | null { 25 // Initialize both pointers at the head of the list 26 let fastPointer: ListNode | null = head; 27 let slowPointer: ListNode | null = head; 28 29 // Traverse the list until fast pointer reaches the end 30 // Check both fast pointer and its next node to avoid null reference 31 while (fastPointer !== null && fastPointer.next !== null) { 32 // Move fast pointer two steps forward 33 fastPointer = fastPointer.next.next; 34 // Move slow pointer one step forward 35 slowPointer = slowPointer.next!; 36 } 37 38 // Return the slow pointer which is now at the middle node 39 return slowPointer; 40} 41

Solution Approach

The implementation uses the fast and slow pointer technique (Floyd's Tortoise and Hare algorithm) to find the middle node in a single pass.

Algorithm Steps:

  1. Initialize two pointers: Both slow and fast start at the head of the linked list

    slow = fast = head
  2. Traverse the linked list with different speeds:

    • The loop continues while both fast and fast.next exist
    • This condition ensures we don't get a null pointer error when fast tries to jump two nodes
    while fast and fast.next:
  3. Move the pointers:

    • slow moves one step forward: slow = slow.next
    • fast moves two steps forward: fast = fast.next.next
    • Python allows simultaneous assignment, so we can write:
    slow, fast = slow.next, fast.next.next
  4. Return the middle node: When the loop exits, slow points to the middle node

    return slow

Why the loop condition works:

  • fast checks if the fast pointer itself exists (prevents error when list has odd length)
  • fast.next checks if fast can make another two-step jump (prevents error when list has even length)
  • When either becomes None, we've reached or passed the end of the list

Example walkthrough:

  • For list 1->2->3->4->5:

    • Initially: slow at 1, fast at 1
    • Iteration 1: slow at 2, fast at 3
    • Iteration 2: slow at 3, fast at 5
    • Loop ends (fast.next is None), return node 3
  • For list 1->2->3->4:

    • Initially: slow at 1, fast at 1
    • Iteration 1: slow at 2, fast at 3
    • Iteration 2: slow at 3, fast is None
    • Loop ends, return node 3 (the second middle node)

Time Complexity: O(n) where n is the number of nodes - we traverse at most n/2 iterations Space Complexity: O(1) - only using two pointers regardless of input size

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 finding the middle node of the linked list: 1 -> 2 -> 3 -> 4 -> 5

Initial Setup:

  • Both slow and fast pointers start at the head (node 1)
  • List visualization: [1] -> 2 -> 3 -> 4 -> 5
    • slow points to node 1
    • fast points to node 1

Iteration 1:

  • Check condition: fast (node 1) exists ✓ and fast.next (node 2) exists ✓
  • Move pointers:
    • slow moves one step: slow = slow.next → now at node 2
    • fast moves two steps: fast = fast.next.next → now at node 3
  • List visualization: 1 -> [2] -> (3) -> 4 -> 5
    • [2] = slow pointer position
    • (3) = fast pointer position

Iteration 2:

  • Check condition: fast (node 3) exists ✓ and fast.next (node 4) exists ✓
  • Move pointers:
    • slow moves one step: now at node 3
    • fast moves two steps: now at node 5
  • List visualization: 1 -> 2 -> [3] -> 4 -> (5)
    • [3] = slow pointer position
    • (5) = fast pointer position

Iteration 3:

  • Check condition: fast (node 5) exists ✓ but fast.next is None ✗
  • Loop terminates
  • Return slow which points to node 3

Result: Node 3 is correctly identified as the middle node of the 5-node list.

For an even-length list like 1 -> 2 -> 3 -> 4, the algorithm would proceed:

  • Start: slow and fast at node 1
  • After iteration 1: slow at node 2, fast at node 3
  • After iteration 2: slow at node 3, fast becomes None (moved past node 4)
  • Returns node 3, which is the second middle node (nodes 2 and 3 are both "middle", we return the second one)

Time and Space Complexity

Time Complexity: O(n) where n is the number of nodes in the linked list. The algorithm uses two pointers - a slow pointer that moves one step at a time and a fast pointer that moves two steps at a time. The fast pointer will reach the end of the list after traversing approximately n/2 iterations (since it moves twice as fast), at which point the loop terminates. Therefore, the slow pointer visits approximately n/2 nodes and the fast pointer visits approximately n nodes, resulting in a total time complexity of O(n).

Space Complexity: O(1) - The algorithm only uses two pointer variables (slow and fast) regardless of the size of the input linked list. No additional data structures or recursive calls are made that would scale with the input size, making the space complexity constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Loop Condition

A common mistake is using only while fast.next: or while fast: as the loop condition instead of while fast and fast.next:.

Why it's wrong:

  • Using only while fast.next: will cause a NullPointerException/AttributeError when fast itself becomes None (happens with odd-length lists)
  • Using only while fast: will cause an error when trying to access fast.next.next if fast.next is None (happens with even-length lists)

Incorrect implementation:

# This will fail for odd-length lists while fast.next: # Error when fast is None  slow = slow.next  fast = fast.next.next

Correct implementation:

# Check both fast and fast.next while fast and fast.next:  slow = slow.next  fast = fast.next.next

2. Moving Pointers in Wrong Order

Another pitfall is updating the pointers in the wrong sequence when not using simultaneous assignment.

Why it's wrong: If you update fast first using sequential assignments, you lose the reference needed for updating slow.

Incorrect implementation:

while fast and fast.next:  fast = fast.next.next # Wrong: updating fast first  slow = slow.next # This still works but can lead to confusion

Better implementations:

# Option 1: Update slow first while fast and fast.next:  slow = slow.next  fast = fast.next.next  # Option 2: Use simultaneous assignment (Python feature) while fast and fast.next:  slow, fast = slow.next, fast.next.next

3. Off-by-One Error for Even-Length Lists

Some might mistakenly think they need special handling for even-length lists to return the second middle node.

Why it's wrong: The algorithm naturally returns the second middle node for even-length lists. Adding extra logic is unnecessary and can introduce bugs.

Incorrect approach:

# Unnecessary complexity def middleNode(self, head):  # Count nodes first  count = 0  current = head  while current:  count += 1  current = current.next   # Find middle based on count  middle_index = count // 2  current = head  for _ in range(middle_index):  current = current.next  return current

Correct approach: The two-pointer technique automatically handles both odd and even cases without special logic.

4. Not Handling Edge Cases

While the algorithm handles most cases well, ensure you consider:

  • Single node list (works correctly as-is)
  • Two node list (returns the second node correctly)

The beauty of the while fast and fast.next: condition is that it handles all these edge cases without additional checks.

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

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

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

Load More