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]A singly linked list is a chain of nodes where each node only knows the address of the next node. We want the node sitting exactly in the middle. The annoying part: a linked list will not tell you how long it is, and you cannot jump to "position 5" the way you can with an array. The only move you are allowed is to start at the head and follow next, one node at a time. So how do you find the centre of something when you don't even know where its end is?
Walk the whole list once and count the nodes. Now you know the length — say 6 — so the middle is node number 3. Go back to the head and walk 3 steps. Done.
length = 0
curr = head
while curr: # first walk: only to measure
length += 1
curr = curr.next
curr = head
for _ in range(length // 2): # second walk: to the middle
curr = curr.next
return currThis is correct, and it is already reasonably fast — each walk touches N nodes, so it is O(N) time. But look at what it actually does: it walks the list once just to learn the length, throws that walk away, then walks the same nodes again. We pass over everything twice. Worth asking: was the length ever really the thing we needed, or only a means to find the halfway point?
Here is the move that removes the counting completely. Start two pointers at the head at the same time. One of them — call it slow — takes a single step each turn. The other — fast — takes two steps each turn. They leave together, but fast pulls ahead because it covers ground twice as quickly.
Now think about the distances, not the nodes. However many steps slow has taken, fast has taken exactly double. So at the very moment fast runs out of list — after covering the full length — slow has covered exactly half of it. And half the length is the middle. We never counted a thing; the speed difference did the measuring for us, in a single pass.
slow = head
fast = head
while fast and fast.next: # can fast still take two steps?
slow = slow.next # one step
fast = fast.next.next # two steps
return slowfast and fast.next before moving, and both halves matter. Because fast jumps two nodes, it needs two nodes ahead of it to be safe — if fast itself is null, or the node right after it is null, there is no room for a double step, so we stop. Checking only fast and then jumping two would run straight off the end and crash. This one guard is also exactly what makes both list shapes come out right:- Odd length (say 5 nodes):
fast lands on the last node, fast.next is null, the loop stops, and slow is on node 3 — the true middle.- Even length (say 6 nodes):
fast steps past the last node to null, the loop stops, and slow is on node 4 — the second of the two middle nodes, which is what the problem asked for.fast.next is null — stop. slow is on node 3, the middle. Return it.Three turns, one pass, and the answer just falls out.
This is your first taste of same-direction two pointers, and it is worth seeing the general shape right now, because a whole run of problems is built on it. The idea: put two pointers at (or near) the front of one sequence and let them both move forward — but not locked together. The gap between them, or their difference in speed, is a fact you control on purpose, and that fact is what solves the problem. (This is the mirror image of the other big two-pointer style, where the pointers start at the two ends and move toward each other — you will meet that one a little later.)
Across the next few problems the "not locked together" part shows up in three concrete shapes, and recognising which shape a new problem wants is most of the work:
- Different speed — fast goes 2×. Exactly what we just did. Because one pointer outruns the other, you learn a positional fact: the middle here, and soon whether a linked list secretly loops back on itself (Linked List Cycle) and where that loop starts (Cycle II) — all without ever measuring the length.
- A fixed gap — fast starts N steps ahead, same speed. Hold the two pointers a constant N apart; the instant the front one reaches the end, the back one is exactly N from the end. That is how you reach the N-th node from the end in one pass (Remove Nth Node From End).
- Slow moves only when there is something worth keeping. The fast pointer reads every element; the slow pointer marks where to write the next element you want to keep, and it steps forward only when fast finds a keeper. This quietly rebuilds an array in place, with no second array — the engine behind Move Zeroes, Remove Duplicates, and Is Subsequence.
When should this pattern jump to mind? The strong signals: the input is a linked list and you're asked for the middle, a loop, or something counted "from the end"; or you're told to rearrange or filter an array in place, in O(1) extra space, keeping the order; or you simply cannot index into the structure or don't know its length up front. All three point at the same move — two pointers walking forward at a distance you control.
One honest limit to remember: the 2× speed trick depends on being able to take two steps safely, which is why the fast and fast.next guard is not optional. And same-direction pointers only work when a single forward sweep is enough to decide everything — if judging one element needs information from further ahead that you haven't seen yet, one forward pass won't cut it.
One pass instead of two, and only two pointers of bookkeeping: O(N) time, O(1) space. It isn't asymptotically faster than count-then-walk — both are O(N) — but it does the job in a single sweep, and, more to the point, it is the very same move you'll reuse to detect cycles and reach end-relative nodes. Train the reflex: the moment you need a positional fact about a linked list and catch yourself wishing you could "just index into it," reach for a second pointer moving at a controlled speed or offset.
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.