Problem overview
The function reverse receives the head of a singly linked list and returns the head of the same list in reverse order. In Rust a list is an Option<Box<ListNode>>: None is the empty list, and Some(box) is a node that owns the rest of the list through its next field. For [1, 2, 3, 4, 5] the result is [5, 4, 3, 2, 1].
The statement asks for O(n) time and O(1) extra space. That means no copying the values into a vector and rebuilding: the existing nodes must be relinked, not recreated.
Approach: move one node at a time onto a new list
A singly linked list only points forward, so you cannot walk it from the right. Instead, reverse every link. Taking 1 -> 2 -> 3 to 1 <- 2 <- 3 is easy to say, but a naive loop that flips node.next loses the rest of the list the moment the link is flipped. The remedy is to detach the rest of the list first and keep hold of it.
Think of two lists: the part you have not processed yet (current) and the reversed part you have built so far (prev). At each step, take the first node of current and put it in front of prev. When current runs out, prev is the answer.
On [1, 2, 3]:
- Start:
prev = [],current = [1, 2, 3]. - After one step:
prev = [1],current = [2, 3]. - After two steps:
prev = [2, 1],current = [3]. - After three steps:
prev = [3, 2, 1],current = [].
Every node is touched once, so the time is O(n). No new nodes are allocated, so the extra space is O(1).
Step by step
- Declare
prevasNone. The reversed part starts empty. The annotationOption<Box<ListNode>>is needed because a bareNonedoes not say what it could hold. - Move
headintocurrent. This is a move, not a copy: from here oncurrentowns the whole list. - Loop with
while let Some(mut node) = current. Each pass moves the first box out ofcurrentintonode. It ismutbecause the loop changes itsnextfield. WhencurrentisNonethe pattern fails and the loop ends. - Detach the rest.
node.next.take()returns the tail and leavesNonebehind innode.next. Assigning it tocurrentgives the loop its next node. Because the pattern movedcurrentout, assigning to it here simply gives it a fresh value. - Reverse the link.
node.next = prevmakes the node own the already reversed part. This is the step that flips the direction. - Make the node the new front.
prev = Some(node)wraps the box back in anOption. - Return
prevas the last expression, with no semicolon.
The solution in Rust
impl Solution {
fn reverse(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
let mut prev: Option<Box<ListNode>> = None;
let mut current = head;
while let Some(mut node) = current {
current = node.next.take();
node.next = prev;
prev = Some(node);
}
prev
}
}
Rust details worth knowing:
- Ownership does the bookkeeping. A node can have only one owner. When
node.next = prevruns,previs moved into the node, and the next line givespreva new value. At no point do two variables own the same node, which is why the code needs nounsafeand no reference counting. take()replaces a value withNone.Option::takeswaps the option forNoneand hands you the old content. It is the standard way to pull a tail out of a node. Herenodeis an owned box, so writingcurrent = node.next;also compiles and gives the same result;take()is the idiom that keeps working when you only hold a&mutto a node, as in the removal problems.while letmoves the value.Some(mut node) = currentmoves the box out ofcurrent. That is fine as long ascurrentis assigned again before the next test, which the first line of the body does.- No semicolon on the last line.
prevalone is the function's value.prev;would make the body evaluate to()and fail to compile.
Complexity
Time: O(n). The loop runs once per node, and each pass does a fixed number of pointer moves.
Space: O(1). The loop keeps three variables, prev, current and node. Boxes are moved between them, never copied.
Edge cases and common mistakes
- Empty list.
reverse(None)skips the loop and returnsprev, which isNone. - One node.
[7]goes through the loop once, withnode.nextset toNone, and comes back as[7]. - Flipping the link before saving the tail. Setting
node.next = prevbefore reading the old tail overwrites the only handle on the rest of the list, so the tail is dropped. Take the tail first. - Returning
currentorhead. After the loopcurrentisNoneandheadhas been moved. The answer isprev.
Testing
[1, 2, 3, 4, 5]gives[5, 4, 3, 2, 1].[7]gives[7].[]gives[].

