Topics › Linked List › Medium Problems of LL
Reverse a LinkedList [Iterative]
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given the head of a singly linked list. Reverse the list and return the new head. Walking forward from the returned head must visit the original values from last to first, and the original head must now be the tail with next == null. The ideal solution rewires the existing nodes in one pass using constant extra space; this article focuses on that iterative method, with the recursive version shown for comparison.
Constraints: 0 ≤ n ≤ 10^5 nodes, values fit in a 32-bit signed integer, no cycles.
Example 1
list = 1 -> 2 -> 3 -> 4 -> 5
answer = 5 -> 4 -> 3 -> 2 -> 1
Example 2
list = 1 -> 2
answer = 2 -> 1 // 2.next is 1, 1.next is null
Example 3
list = (empty)
answer = (empty)
The node type is the standard one with an int val and a ListNode next reference.
static class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
Approach 1: Brute force, reverse the values
Intuition. If only the sequence of values matters, the nodes can stay where they are and the values can be moved. A stack is a device that reverses order: push every value in a forward pass, then pop in a second forward pass and overwrite each node’s value. The first node receives the last value pushed, which belonged to the tail.
Algorithm.
- Traverse and push each
valonto a stack. - Traverse again from the head; at each node, pop the stack into
val. - Return the same head.
static ListNode bruteForce(ListNode head) {
Deque<Integer> stack = new ArrayDeque<>();
for (ListNode t = head; t != null; t = t.next) stack.push(t.val);
for (ListNode t = head; t != null; t = t.next) t.val = stack.pop(); // pops come out in reverse order
return head;
}
Complexity. Time O(n): two passes. Space O(n) for the stack.
This passes any test that compares values, and the Java file checks it against the same expected output as the other approaches. But it does not reverse the list in the structural sense: the head node object is still the head (the tests assert bruteForce(same) == same). When other code holds references to particular nodes, or when the problem is a building block for group reversal or palindrome checking, the links themselves must change. The method also uses linear extra memory where none is needed.
Approach 2: Optimal, iterative pointer reversal
Intuition. Reversing a singly linked list means every node’s next should point at the node that used to come before it. A singly linked node does not know its predecessor, so we carry it along in a variable prev as we walk. At each node we redirect next to prev and then move forward. The one trap is that redirecting next destroys our only path to the rest of the list, so the old next must be saved into a temporary before the redirect. When the walk ends, prev is standing on the last node we processed, the old tail, which is the new head.
Algorithm.
- Set
prev = nullandcurrent = head. - While
current != null:- save
next = current.next; - set
current.next = prev; - move
prev = currentandcurrent = next.
- save
- Return
prev.
static ListNode optimal(ListNode head) {
ListNode prev = null, current = head;
while (current != null) {
ListNode next = current.next; // save the rest of the list before cutting the link
current.next = prev; // flip this node's pointer
prev = current; // advance both pointers
current = next;
}
return prev; // prev ends on the old tail, which is the new head
}
Complexity. Time O(n): each node is visited once with constant work. Space O(1): three references regardless of n.
Dry run on 1 -> 2 -> 3. Each row shows the state at the end of an iteration.
| iteration | current flipped | its new next | prev | current moves to | reversed part |
|---|---|---|---|---|---|
| 1 | 1 | null |
1 | 2 | 1 -> null |
| 2 | 2 | 1 | 2 | 3 | 2 -> 1 -> null |
| 3 | 3 | 2 | 3 | null |
3 -> 2 -> 1 -> null |
The loop ends with current == null and prev on node 3, which is returned. The old head 1 now has next == null, so it is the tail. The test file checks these facts on real node objects: newHead == oldTail, head.next == null, and newHead.next.next == head.
A useful way to see why the invariant holds: at the start of every iteration, prev is the head of a correctly reversed list made of all nodes already processed, and current is the head of the untouched remainder. The body moves exactly one node from the front of the remainder to the front of the reversed list, which is the insert-at-head operation. After n moves the remainder is empty and the reversed list is complete.
The first assignment prev = null is what makes the old head become the tail: its next is set to prev while prev is still null. The empty list needs no special case: current starts as null, the loop does not run, and prev = null is returned. A single node has its next set to null (unchanged) and is returned.
Reversing twice must restore the original order, and the tests do that on every input as a cheap consistency check.
For comparison: the recursive version
Intuition. Recursion expresses the same idea from the other end. Assume the rest of the list (from the second node onward) has already been reversed and its new head returned. The old second node is now the tail of that reversed part, and head should hang behind it: head.next.next = head. Then head becomes the overall tail, so its own next is cleared. The base case is a list of zero or one node, which is its own reverse.
Algorithm.
- If
headisnullor has no successor, returnhead. - Let
newHead = recursive(head.next). - Set
head.next.next = headandhead.next = null. - Return
newHead.
static ListNode recursive(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newHead = recursive(head.next);
head.next.next = head; // the old second node now points back at head
head.next = null; // head becomes the tail
return newHead;
}
Complexity. Time O(n). Space O(n) on the call stack, one frame per node, because the pointer fix-up happens after the recursive call returns.
That stack is the reason the iterative version is the one to write. The default Java thread stack handles a few thousand frames (the tests use 5000), but a list of 10^5 nodes risks StackOverflowError. The iterative loop has no such limit; the tests run it, and the brute force, on 200000 nodes.
Edge cases and pitfalls
- Empty list.
nullin,nullout, with no special case needed in the iterative version. - Single node. Its own reverse;
nextstaysnull. - Two nodes. The smallest case where a link actually moves: the old second node must point at the old first, which must point at
null. - Losing the rest of the list. Writing
current.next = prevbefore savingcurrent.nextcuts the list; the loop then ends after one node. Always savenextfirst. - Returning the wrong head. The new head is
prev, notheadand notcurrent(which isnullat the end). - Old head’s
next. It must end asnull. Starting withprev = nullguarantees it; startingprevatheadcreates a two-node cycle. - Equal values.
4 -> 4 -> 4reversed looks identical by values; the tests verify node identities as well. - Brute force is not a structural reversal. It moves values and keeps node identities fixed, which can be wrong when callers hold node references.
- Deep recursion. Avoid the recursive version on long inputs.
Related problems
- Reverse a LL is the SDE and Blind 75 entry for the same problem, with more on the recursive view.
- Reverse a Doubly Linked List is simpler in one way, since every node already knows its predecessor.
- Reverse LL in group of given size K applies this exact loop to consecutive blocks of
knodes. - Check if LL is palindrome or not reverses the second half of a list and compares.
- Add one to a number represented by LL reverses to process digits from the least significant end.
- Insertion at the head of Linked List is the single step that the iterative loop repeats
ntimes.