Middle of the Linked List
Given the head of a singly linked list, return the middle node of the linked list. If there are two middle nodes, return the second middle node.
- The number of nodes in the list is in the range [1, 100]
- 1 <= Node.val <= 100
- Solve in a single pass
head = [1,2,3,4,5][3,4,5]head = [1,2,3,4,5,6][4,5,6]head = [1][1]We have a singly linked list, and our goal is to find its exact middle node. But there is a catch: unlike an array where we can instantly check the length and jump to the middle element in a split second, a linked list is a blind chain. We cannot query its size or jump to an arbitrary index without physically walking the nodes one by one. How do we locate the center of a structure when we don't know how long it actually is?
The most straightforward approach is to solve this in two separate trips. First, we traverse the entire list from head to tail to count how many nodes exist. Once we have the total length, we calculate the midpoint index. Then, we reset our pointer to the start and take a second trip, walking exactly halfway through the list to arrive at the middle node.
While this successfully finds the center, it forces us to read the list twice. It works, but making two full trips is highly inefficient.
# Trip 1: Count the nodes
length = 0
curr = head
while curr:
length += 1
curr = curr.next
# Trip 2: Walk to the middle
mid_index = length // 2
curr = head
for _ in range(mid_index):
curr = curr.next
return currTo find the middle in a single pass, we introduce two distinct pointers that traverse the list simultaneously. Each pointer has a strict contract:
- The Slow Pointer (slow): Acts as our steady jogger. It advances exactly one node at a time.
- The Fast Pointer (fast): Acts as our sprinter. It surges forward exactly two nodes at a time.
Both pointers start side-by-side at the head of the list.
The reason this configuration works comes down to a simple mathematical distance relationship. Because the fast pointer moves exactly twice as fast as the slow pointer, the total distance covered by fast will always be exactly double the distance covered by slow.
Therefore, when fast reaches the absolute end of the list (distance D), slow must have traveled exactly half that distance (D/2). Without ever counting the total number of nodes, the steady runner is naturally guided to the exact center.
At each step, we first verify that the fast pointer has a valid next step to take, ensuring we do not encounter a null pointer exception. If it is safe to proceed, we advance both pointers in unison: we move slow forward by one link, and fast forward by two links.
slow = head
fast = head
# Sprint and jog in unison until fast reaches the end
while fast and fast.next:
slow = slow.next # Advance 1 step
fast = fast.next.next # Advance 2 steps
# slow is now sitting perfectly at the midpoint
return slowThe movement rules naturally handle different list structures without any extra branching logic:
- Odd-length list (e.g., 5 nodes): fast lands exactly on the tail node (Node 5). Since fast.next is null, the loop terminates. slow sits perfectly on Node 3 (the exact middle).
- Even-length list (e.g., 6 nodes): fast sprints past the tail and lands on null. The loop terminates. slow sits on Node 4, which is the second of the two middle nodes, perfectly matching the problem's criteria.
- Single-node list (1 node): From the very start, fast.next is null. The loop never executes, and slow (still pointing to head) is immediately returned.
Linear Discovery via 2-Pointer Speeds
Current Progress
Phase 1: Initialization
Both Slow and Fast pointers start at the head. In this race, the speeds are intentionally uneven to find the balance point.
If Pointer A moves @ speed 1 and Pointer B moves @ speed 2, when B hits the end ($L$), A will be at $L/2$. No counting nodes required!
Traditional solutions use two passes (count then find). This method finds the middle in exactly one pass.