Linked List · 26 of 36
Can we reverse a linked list in less than O(n)
Can a chain of boxes be turned around faster than touching every box? Not a one way chain, but a two way chain can start reading from the other end instead.
- Simple way
- time O(n)
room O(1) - Clever way
- time O(1)
room O(1)
The problem, simply
This is a thinking question. Normal reversal walks the whole list and flips every arrow, which takes O(n) time. Can we reverse a linked list faster than that?
The honest answer has two parts. For a plain singly linked list, no: there are n arrows that each have to change, and you cannot change an arrow without walking to it. For a doubly linked list that keeps both a head and a tail, yes in a sense: you do not need to move any arrow at all. Flip one switch that says "read from the other end", and the list behaves reversed in O(1).
A story to remember it
Picture it
A one way list. Reversing it means all five arrows must point the other way, and each one can only be reached by walking there.
- checking
A two way list with a head and a tail. You can already walk it from 5 back to 1. Nothing needs to be rewired to read it backwards.
- looking now
Examples
A singly linked list
- In
1 -> 2 -> 3 -> 4 -> 5- Out
5 -> 4 -> 3 -> 2 -> 1, in O(n)
Every one of the 5 arrows must change, so no method can beat O(n).
A doubly linked list with a direction flag
- In
1 <-> 2 <-> 3 <-> 4 <-> 5, then reverse()- Out
reads 5 4 3 2 1, in O(1)
reverse() flips one flag. The boxes and arrows stay exactly where they are.
An XOR linked list
- In
each box stores prev XOR next, plus head and tail- Out
swap head and tail, in O(1)
An XOR list is walked the same way in both directions, so swapping the two ends is a full reversal.
Tiny lists
- In
empty, or a single box- Out
nothing to do
Zero or one box is already its own reverse.
Way 1: the simple way
The normal reversal with three fingers: prev, curr, next. Save next, flip the arrow, slide forward.
prev = null,curr = head.- While
curris notnull: savenext = curr.next, setcurr.next = prev, then moveprevandcurrone step. - Return
prev.
Why nothing can be faster on a one way list: the answer has n arrows, and every one of them is different from before. Changing an arrow means standing on its box, and the only way to reach a box is to walk there from the head. So at least n steps are needed, and this way uses exactly that many.
Halfway through. Boxes 1 to 3 are already flipped. The rest still point forward, and the only way to reach them is to keep walking.
- looking now
- done
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null, curr = head;
while (curr != null) {
ListNode next = curr.next; // hold on to the rest
curr.next = prev; // flip one arrow
prev = curr;
curr = next;
}
return prev; // every one of the n arrows changed
}
}How long it takes: one step per box, O(n), which is the best possible here. Extra room: three fingers, O(1).
Way 2: the clever way
Change the question. Most of the time nobody needs the arrows to physically point the other way. They need the list to act reversed: read from the other end, add to the other end. A doubly linked list that keeps head, tail and one flag can do that.
- Keep
head,tailand a flagflipped, which starts asfalse. reverse()only doesflipped = !flipped. That isO(1).- Every operation asks the flag which way to go. The front is
flipped ? tail : head. The box afterbisflipped ? b.prev : b.next. Adding at the end goes after the tail normally, or before the head when flipped.
Reverse by flipping a switch, not arrows
01 / 09
A two way list 1 2 3 4 5 with head and tail. The flag is off, so reading starts at head and follows next arrows.
- flipped
- false
- looking now
- done
- answer
- just added
class FlipList {
static class Box {
int val;
Box prev, next;
Box(int val) { this.val = val; }
}
private Box head, tail;
private boolean flipped = false; // true means read from tail to head
public void reverse() { flipped = !flipped; } // the whole reversal: O(1)
private Box front() { return flipped ? tail : head; }
private Box after(Box b) { return flipped ? b.prev : b.next; }
public void addLast(int val) {
Box b = new Box(val);
if (head == null) { head = tail = b; return; }
if (!flipped) { tail.next = b; b.prev = tail; tail = b; } // real end is the tail
else { b.next = head; head.prev = b; head = b; } // real end is the head
}
public List<Integer> toList() {
List<Integer> out = new ArrayList<>();
for (Box b = front(); b != null; b = after(b)) out.add(b.val);
return out;
}
}How long it takes: reverse() is O(1). Reading the list is still O(n), but it was O(n) anyway. Extra room: one flag, O(1).
Watch out for
- Code that walks the boxes directly with
.nextskips the flag and sees the old order. All access must go through the wrapper. - The flag does not help a singly linked list. There is no
prevarrow to follow, so there is nothing to switch to. - Do not claim
O(1)reversal without saying what you changed: a different structure (two way, with a tail and a flag), not a faster way to flip the same arrows.
What interviewers ask next
- What about an XOR linked list? Each box stores
prev XOR nextas one number. Walking needs the previous box, and it works the same in both directions. So swappingheadandtailis a completeO(1)reversal, no flag needed. - Can many processors do it faster? If you already have all
nboxes in an array,nworkers can each flip one arrow at the same moment,O(1)time withnworkers. The total work is stillnflips. - Where is this idea used for real? Java 21 added
reversed()on lists and deques. It returns a reversed view inO(1)that reads the same data from the other end, just like the flag. Deques in general let you treat either end as the front. - Reverse a sublist from position
mtonin one pass. That needs real arrow flipping: walk tom - 1, flipn - m + 1arrows, and stitch both ends. - Reverse a list using recursion. Same
O(n)time, but the waiting calls useO(n)stack.
Remember this
Grown-up words: the bookshelf note is a direction flag (a reversed view), the one way strings are a singly linked list, the two way shelf is a doubly linked list with head and tail pointers, and "at least n steps" is a lower bound on the running time.