Reverse Linked List
Walk the list once and turn each arrow around, holding on to the next node before you cut it. Solved two ways, copying the values in O(n) time and O(n) space, and in place in O(n) time and O(1) space, with a step-through, code in five languages and the Big O worked out from the code.
The problem
Section titled “The problem”You are given the head of a singly linked list. A singly linked list is a chain of nodes. Each
node holds a value and a next pointer to the node after it. The last node points to null.
Reverse the list and return the new head.
Example 1
Input: head = [3, 8, 1, 6, 4]
Output: [4, 6, 1, 8, 3]
Every arrow now points the other way. The old tail, 4, is the new head.
Example 2
Input: head = [2, 9]
Output: [9, 2]
Two nodes swap places.
Example 3
Input: head = []
Output: []
An empty list stays empty.
Constraints
Section titled “Constraints”- 0 ≤ n ≤ 5,000, where n is the number of nodes
- −5,000 ≤ Node.val ≤ 5,000
Clarifying questions
Section titled “Clarifying questions”- Can the list be empty?
- Yes. Then head is null, and we return null.
- May we change the nodes, or must we build a new list?
- Changing the nodes is fine. We return the head of the reversed list.
- Singly or doubly linked?
- Singly. Each node only knows the node after it, not the one before.
- Can the list have a cycle?
- No. It always ends at null.
Spot the pattern
Section titled “Spot the pattern”A singly linked list can only be walked forward. To reverse it, each node must end up pointing at the node before it. So while walking, keep a pointer to the previous node, and turn one arrow at each step. This is the core linked list move, and many harder problems reuse it.
Solutions
Section titled “Solutions”Recursion also reverses the list in O(n) time, but its call stack holds n calls, so it uses O(n) memory like the copy.
Copy the values O(n), O(n) space
The idea
Section titled “The idea”Walk the list and copy every value into an array. Then walk it again from the head, and write the values back, taking them from the end of the array. The first node gets the last value, and so on.
The arrows never move, only the values do. It is simple and correct, but the array costs n extra slots.
Walkthrough
Section titled “Walkthrough”- values = []
- node = head
- while node:
- values.append(node.val)
- node = node.next
- node = head
- while node:
- node.val = values.pop()
- node = node.next
- return head
Pass 1 reads every value into an array. Pass 2 writes them back from the end of the array.
class Solution { // Copy the values: read them into an array, then write them back in reverse. // Two passes, so O(n) time, but the array costs O(n) memory. public ListNode reverseList(ListNode head) { java.util.List<Integer> values = new java.util.ArrayList<>(); for (ListNode node = head; node != null; node = node.next) values.add(node.val); // The last value read must go into the first node, so take from the end. for (ListNode node = head; node != null; node = node.next) { node.val = values.remove(values.size() - 1); } return head; }}# Copy the values: read them into an array, then write them back in reverse.# Two passes, so O(n) time, but the array costs O(n) memory.def reverse_list(head: ListNode | None) -> ListNode | None: values = [] node = head while node: values.append(node.val) node = node.next # The last value read must go into the first node, so pop from the end. node = head while node: node.val = values.pop() node = node.next return headpublic class Solution{ // Copy the values: read them into an array, then write them back in reverse. // Two passes, so O(n) time, but the array costs O(n) memory. public ListNode ReverseList(ListNode head) { var values = new System.Collections.Generic.List<int>(); for (var node = head; node != null; node = node.next) values.Add(node.val); // The last value read must go into the first node, so take from the end. for (var node = head; node != null; node = node.next) { node.val = values[values.Count - 1]; values.RemoveAt(values.Count - 1); } return head; }}// Copy the values: read them into an array, then write them back in reverse.// Two passes, so O(n) time, but the array costs O(n) memory.func reverseList(head *ListNode) *ListNode { values := []int{} for node := head; node != nil; node = node.Next { values = append(values, node.Val) } // The last value read must go into the first node, so take from the end. for node := head; node != nil; node = node.Next { node.Val = values[len(values)-1] values = values[:len(values)-1] } return head}// Copy the values: read them into an array, then write them back in reverse.// Two passes, so O(n) time, but the array costs O(n) memory.function reverseList(head: ListNode | null): ListNode | null { const values: number[] = []; for (let node = head; node; node = node.next) values.push(node.val); // The last value read must go into the first node, so take from the end. for (let node = head; node; node = node.next) node.val = values.pop()!; return head;}1 Find the work that repeats
Two loops run one after the other, not one inside the other. Each run of the first reads one value. Each run of the second writes one value. Both are 1 step.
- values = []
- node = head
- while node:
- values.append(node.val)
- node = node.next
- node = head
- while node:
- node.val = values.pop()
- node = node.next
- return head
2 Count the runs on Example 1
Example 1 has 5 nodes. The first loop reads 5 values. The second writes 5 values.
5 + 5 = 10 runs.
node 0 1 2 3 4 pass 1 reads 3 8 1 6 4 pass 2 writes 4 6 1 8 3 total5 + 5 = 103 Write the count for any n
Each loop visits every node once:
n + n = 2nCheck it with Example 1: 2 × 5 = 10.
4 Keep only the part that grows fastest
Drop the 2. Two passes instead of one do not change how the work grows. What is left is n.
So the time is O(n).
S Space: count the extra memory the same way
The array
valuesholds one value per node, so it grows to n slots. The space is O(n).values (n values)node
In place O(n), O(1) space
The idea
Section titled “The idea”Walk the list with two pointers: previous (the node behind) and current (the node we are on). At each
node, turn its arrow back to previous. Then step both pointers forward.
There is one trap. Once current.next points back, the rest of the list is unreachable. So save
current.next in next_node before changing it.
When current falls off the end, previous is the old tail, which is the new head.
The invariant: before each step, the nodes up to previous form a reversed list, and current is the
head of the part not yet touched.
Walkthrough
Section titled “Walkthrough”- previous, current = None, head
- while current:
- next_node = current.next
- current.next = previous
- previous = current
- current = next_node
- return previous
previous starts at null and current at the head. Everything left of current will be reversed. Everything from current on is untouched.
class Solution { // In place: walk once and turn each arrow back to the node before it. // One pass and three pointers, so O(n) time and O(1) memory. public ListNode reverseList(ListNode head) { ListNode previous = null, current = head; while (current != null) { // Save the rest of the list before we cut the link to it. ListNode nextNode = current.next; current.next = previous; previous = current; current = nextNode; } // current fell off the end, so previous is the old tail: the new head. return previous; }}# In place: walk once and turn each arrow back to the node before it.# One pass and three pointers, so O(n) time and O(1) memory.def reverse_list(head: ListNode | None) -> ListNode | None: previous, current = None, head while current: # Save the rest of the list before we cut the link to it. next_node = current.next current.next = previous previous, current = current, next_node # current fell off the end, so previous is the old tail: the new head. return previouspublic class Solution{ // In place: walk once and turn each arrow back to the node before it. // One pass and three pointers, so O(n) time and O(1) memory. public ListNode ReverseList(ListNode head) { ListNode previous = null, current = head; while (current != null) { // Save the rest of the list before we cut the link to it. ListNode nextNode = current.next; current.next = previous; previous = current; current = nextNode; } // current fell off the end, so previous is the old tail: the new head. return previous; }}// In place: walk once and turn each arrow back to the node before it.// One pass and three pointers, so O(n) time and O(1) memory.func reverseList(head *ListNode) *ListNode { var previous *ListNode current := head for current != nil { // Save the rest of the list before we cut the link to it. nextNode := current.Next current.Next = previous previous, current = current, nextNode } // current fell off the end, so previous is the old tail: the new head. return previous}// In place: walk once and turn each arrow back to the node before it.// One pass and three pointers, so O(n) time and O(1) memory.function reverseList(head: ListNode | null): ListNode | null { let previous: ListNode | null = null; let current = head; while (current) { // Save the rest of the list before we cut the link to it. const nextNode = current.next; current.next = previous; previous = current; current = nextNode; } // current fell off the end, so previous is the old tail: the new head. return previous;}1 Find the work that repeats
Each run of the loop does four pointer moves. That is the same small job every time, so each run is 1 step. There is one loop.
- previous, current = None, head
- while current:
- next_node = current.next
- current.next = previous
- previous = current
- current = next_node
- return previous
2 Count the runs on Example 1
Example 1 has 5 nodes.
currentvisits each of them once, then becomes null and the loop stops.5 runs.
run 1 run 2 run 3 run 4 run 5 stop current 3 8 1 6 4 null 3 Write the count for any n
The loop runs once per node:
n timesCheck it with Example 1: 5 nodes, 5 runs.
4 Keep only the part that grows fastest
There is nothing to drop. The count is already n.
So the time is O(n).
S Space: count the extra memory the same way
The code keeps three pointers:
previous,currentandnext_node. That is 3 variables, for any n, so the space is O(1). No new nodes are made.previouscurrentnext_node
Tested at the limits
Section titled “Tested at the limits”2,000 random lists of 0 to 8 nodes, so the empty list and the single node come up often. Copying the values and reversing in place gave the same list every time.
| Largest inputs | Result |
|---|---|
| 5,000 nodes (the most allowed). In place. | Under 0.001 s, and under 0.001 MB of extra memory: just three pointers. |
| 5,000 nodes. Copy the values. | Under 0.001 s, and about 0.04 MB of extra memory: an array of 5,000 values, which is the O(n) space. |
The designbeyondcode digest, every Saturday
New deep dives, interview tips and site news in one short email. Free. Leave with one click.
We never sell or share your email. We only use it to send this newsletter.