Topics › Linked List › Learn Doubly LinkedList
Reverse a Doubly Linked List
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given the head of a doubly linked list. Reverse the order of its nodes and return the head of the reversed list. Afterwards, walking forward from the returned head must visit the original values from last to first, every prev link must mirror the corresponding next link, and the new head’s prev must be null.
Constraints: 0 ≤ n ≤ 10^5 nodes, values fit in a 32-bit signed integer. The ideal solution uses O(1) extra space and rewires the existing nodes rather than allocating new ones.
Example 1
list = 1 <-> 2 <-> 3 <-> 4 <-> 5
answer = 5 <-> 4 <-> 3 <-> 2 <-> 1
Example 2
list = 1
answer = 1 // a single node is its own reverse
Example 3
list = (empty)
answer = (empty)
The node type carries a value and two references.
static class Node {
int val;
Node prev, next;
Node(int val) { this.val = val; }
Node(int val, Node prev, Node next) { this.val = val; this.prev = prev; this.next = next; }
}
Approach 1: Brute force, reverse the values
Intuition. The simplest way to make the list read backwards is to leave the nodes where they are and move the values. A stack reverses order for free: push every value during a forward pass, then pop during a second forward pass and write the popped value into each node. The first node receives the last value pushed, which is the old tail’s value, and so on. No link changes at all, so the list is trivially still well formed.
Algorithm.
- Traverse the list and push each
valonto a stack. - Traverse again from the head; at each node, pop the stack and store the value in
val. - Return the same head.
static Node bruteForce(Node head) {
Deque<Integer> stack = new ArrayDeque<>();
for (Node t = head; t != null; t = t.next) stack.push(t.val);
for (Node 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 of values.
This is a legitimate answer when only the sequence of values matters, but it does not actually reverse the list: the head node object is still the head (the tests check bruteForce(same) == same). In problems where other code holds references to specific nodes, or where the node payload is large and should not be copied, moving values is the wrong tool. It also costs O(n) extra memory where the optimal approach needs none.
Approach 2: Optimal, swap prev and next in every node
Intuition. In a doubly linked list every node already knows both of its neighbours. Reversing the list means that what used to be “after” should now be “before” and vice versa, and that is exactly a swap of the two fields prev and next inside each node. No node needs to look at any other node to do its own swap. The only subtlety is navigation: once next is overwritten we can no longer use it to move forward, so the old next must be saved before the swap. The last node we visit is the old tail, and after its swap it has prev == null, so it is the new head.
Algorithm.
- Set
current = headandnewHead = head. - While
current != null:- save
oldNext = current.next; - set
current.next = current.prevandcurrent.prev = oldNext; - record
newHead = current(the most recently flipped node); - advance
current = oldNext.
- save
- Return
newHead.
static Node optimal(Node head) {
Node current = head;
Node newHead = head;
while (current != null) {
Node oldNext = current.next; // remember where to go before the links are flipped
current.next = current.prev; // flip
current.prev = oldNext; // flip
newHead = current; // whichever node we flip last is the old tail = new head
current = oldNext;
}
return newHead;
}
Complexity. Time O(n): each node is visited once and does constant work. Space O(1): three references.
Dry run on 1 <-> 2 <-> 3. Each row shows the state after the node in the first column has been flipped.
| flipped | its prev | its next | newHead | current moves to |
|---|---|---|---|---|
| 1 | [2] |
null |
[1] |
[2] |
| 2 | [3] |
[1] |
[2] |
[3] |
| 3 | null |
[2] |
[3] |
null |
Reading forward from newHead = 3: 3 -> 2 -> 1 -> null. Reading prev from 1: 1.prev = 2, 2.prev = 3, 3.prev = null. Every forward link is mirrored, so the list is well formed, and the old head 1 is now the tail with next == null. The tests confirm these facts on real node objects (newHead == oldTail, head.next == null) and run a full backward traversal plus a well-formedness check on every result.
Why the empty and single-node lists need no special case: for null, the loop never runs and newHead = head = null. For one node, both links are null, swapping them changes nothing, and the node is returned as the head.
Reversing twice must restore the original list, which is a cheap and strong self-test; the Java file does it on every input.
Approach 2, the classic form
Intuition. The version most often shown in TUF and textbooks performs the same swap but navigates through current.prev after the swap instead of a saved variable, and keeps a single last reference. After the loop, last holds the old tail’s old prev, which after the swap is that node’s next; so last.prev is the old tail, the new head. It is terser, but the reasoning about which pointer means what after the swap is harder to follow, which is why the version above is presented first.
Algorithm.
- If the list has fewer than two nodes, return it as is.
- For each node: save
last = current.prev, swapcurrent.prevandcurrent.nextvialast, then movecurrent = current.prev(the oldnext). - Return
last.prev.
static Node optimalClassic(Node head) {
if (head == null || head.next == null) return head;
Node last = null;
Node current = head;
while (current != null) {
last = current.prev;
current.prev = current.next;
current.next = last;
current = current.prev; // prev now holds the old next
}
return last.prev; // last is the old tail's old prev; after the swap its prev is the old tail
}
Complexity. Time O(n), space O(1). It is tested against the same expected output as optimal and bruteForce on every input.
The early return exists because last.prev would be a null dereference for an empty list, and for a single node last would also be null. In the first approach, tracking newHead explicitly removes both special cases.
Edge cases and pitfalls
- Empty list.
nullin,nullout. The classic form needs the explicit guard; thenewHeadform does not. - Single node. Its own reverse. Swapping two
nulllinks is harmless. - Two nodes. The smallest case where links actually change; after reversal the old second node must have
prev == nulland the old first nodenext == null. - Losing the way forward. Overwriting
nextbefore saving it leaves the loop with no way to continue. SaveoldNextfirst, or deliberately navigate through the swappedprevas the classic form does. - Returning the wrong head. The head of the reversed list is the node flipped last, not
head. Returningheadgives the old head, which is now the tail. - Half-updated links. Swapping only
nextyields a list that is correct forward and corrupt backward. Test with a backward traversal. - Equal values.
3 <-> 3 <-> 3reversed is indistinguishable by values; the tests check link structure, not just values. - Brute force is not a reversal. The stack method moves values and keeps node identities fixed, which may be wrong if callers hold node references.
Related problems
- Reverse a LinkedList [Iterative] is the singly linked version, where the missing
prevmust be carried in a variable. - Reverse a LL covers the recursive singly linked reversal as well.
- Introduction to Doubly LL introduces the node type and the mirror invariant tested here.
- Check if LL is palindrome or not uses reversal of half a list as a sub-step.
- Reverse LL in group of given size K applies reversal to consecutive blocks.