206. Reverse Linked List
Problem Description
You are given the head
of a singly linked list. Your task is to reverse the entire linked list and return the head of the reversed list.
In a singly linked list, each node contains a value and a pointer to the next node. The reversal means that all the arrows pointing from one node to the next should be flipped in the opposite direction.
For example:
- If the original list is:
1 -> 2 -> 3 -> 4 -> 5
- The reversed list should be:
5 -> 4 -> 3 -> 2 -> 1
The solution uses a head insertion technique with a dummy node. Here's how it works:
- Create a dummy node that will serve as a temporary head
- Traverse through the original list one node at a time
- For each node visited:
- Save the reference to the next node in the original list
- Insert the current node right after the dummy node (at the beginning of the new list)
- Move to the saved next node
- After processing all nodes,
dummy.next
points to the new head of the reversed list
The key insight is that by always inserting nodes right after the dummy node, we naturally build the list in reverse order. Each new node becomes the new first node of the reversed portion.
Intuition
When we need to reverse a linked list, we essentially want to flip all the arrows between nodes. The challenge is that in a singly linked list, we can only traverse forward, and once we change a node's next
pointer, we lose access to the rest of the original list.
Think about how you would reverse a stack of papers: you'd take papers from the top of the original stack and place them one by one onto a new stack. The first paper from the original becomes the last in the new stack, and the last paper from the original becomes the first in the new stack.
We can apply a similar principle here using a "head insertion" strategy. Imagine we have a new empty list (represented by a dummy node), and we want to build the reversed list by always adding nodes at the beginning of this new list.
As we traverse the original list from head to tail:
- The first node we encounter (originally at position 1) gets inserted at the front of our new list
- The second node (originally at position 2) also gets inserted at the front, pushing the first node to position 2
- This continues, with each subsequent node pushing all previous nodes one position back
By consistently inserting at the head position, we naturally achieve the reversal. The last node we process (the tail of the original list) ends up at the front of our new list, becoming the new head.
The dummy node technique is particularly elegant here because it eliminates edge cases. We don't need special handling for an empty list or the first node - the dummy node provides a consistent anchor point where dummy.next
always points to the current head of our reversed portion.
Solution Approach
The implementation uses the head insertion method with a dummy node to reverse the linked list. Let's walk through the algorithm step by step:
Initial Setup:
- Create a dummy node with
dummy = ListNode()
. This node serves as a placeholder before the head of our reversed list - Set
curr = head
to start traversing from the beginning of the original list
Main Reversal Loop:
The while curr:
loop continues until we've processed all nodes:
-
Save the next node:
next = curr.next
- We must save the reference to the next node before modifying any pointers, otherwise we'd lose access to the rest of the original list
-
Insert current node at the head:
curr.next = dummy.next
- Point the current node's
next
to whatever is currently after the dummy node (initiallyNone
, then the previously inserted nodes)
- Point the current node's
-
Update dummy to point to current:
dummy.next = curr
- Make the dummy node point to the current node, effectively placing it at the beginning of our reversed list
-
Move to the next node:
curr = next
- Advance to the saved next node to continue the traversal
Return the result:
After the loop completes, dummy.next
points to the head of the fully reversed list.
Example walkthrough with list 1 -> 2 -> 3
:
- Initially:
dummy -> None
,curr = 1
- Iteration 1: Insert node 1 →
dummy -> 1 -> None
,curr = 2
- Iteration 2: Insert node 2 →
dummy -> 2 -> 1 -> None
,curr = 3
- Iteration 3: Insert node 3 →
dummy -> 3 -> 2 -> 1 -> None
,curr = None
- Return
dummy.next
which points to node 3 (the new head)
The time complexity is O(n)
where n
is the number of nodes, as we visit each node exactly once. The space complexity is O(1)
since we only use a constant amount of extra space regardless of the input size.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through reversing the linked list 1 -> 2 -> 3
using the head insertion approach:
Initial State:
- Original list:
1 -> 2 -> 3 -> None
- Create dummy node:
dummy -> None
- Set
curr = 1
(pointing to first node)
Iteration 1: Processing Node 1
- Save next:
next = 2
(save reference to node 2) - Insert node 1 after dummy:
curr.next = dummy.next
→ Node 1 now points toNone
dummy.next = curr
→ Dummy now points to node 1
- Result:
dummy -> 1 -> None
- Move forward:
curr = 2
Iteration 2: Processing Node 2
- Save next:
next = 3
(save reference to node 3) - Insert node 2 after dummy:
curr.next = dummy.next
→ Node 2 now points to node 1dummy.next = curr
→ Dummy now points to node 2
- Result:
dummy -> 2 -> 1 -> None
- Move forward:
curr = 3
Iteration 3: Processing Node 3
- Save next:
next = None
(node 3 has no next) - Insert node 3 after dummy:
curr.next = dummy.next
→ Node 3 now points to node 2dummy.next = curr
→ Dummy now points to node 3
- Result:
dummy -> 3 -> 2 -> 1 -> None
- Move forward:
curr = None
Final Result:
- Loop ends since
curr = None
- Return
dummy.next
which points to node 3 - Reversed list:
3 -> 2 -> 1 -> None
Notice how each node gets inserted at the front (right after dummy), naturally building the list in reverse order. The first node processed (1) ends up at the tail, while the last node processed (3) becomes the new head.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7class Solution:
8 def reverseList(self, head: ListNode) -> ListNode:
9 """
10 Reverses a singly-linked list iteratively.
11
12 Args:
13 head: The head node of the linked list to reverse.
14
15 Returns:
16 The new head of the reversed linked list.
17 """
18 # Create a dummy node to simplify insertion at the beginning
19 dummy_node = ListNode()
20
21 # Pointer to traverse the original list
22 current = head
23
24 # Iterate through each node in the original list
25 while current:
26 # Store the next node before we modify current's next pointer
27 next_node = current.next
28
29 # Insert current node at the beginning of the reversed list
30 # Point current's next to whatever dummy is pointing to
31 current.next = dummy_node.next
32
33 # Make dummy point to the current node (new head of reversed list)
34 dummy_node.next = current
35
36 # Move to the next node in the original list
37 current = next_node
38
39 # Return the head of the reversed list (dummy's next)
40 return dummy_node.next
41
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 /**
13 * Reverses a singly linked list iteratively.
14 *
15 * @param head The head of the original linked list
16 * @return The head of the reversed linked list
17 */
18 public ListNode reverseList(ListNode head) {
19 // Create a dummy node to simplify the reversal process
20 // The dummy node will point to the reversed list
21 ListNode dummyNode = new ListNode();
22
23 // Initialize current pointer to traverse the original list
24 ListNode current = head;
25
26 // Iterate through each node in the original list
27 while (current != null) {
28 // Store the next node before breaking the link
29 ListNode nextNode = current.next;
30
31 // Insert current node at the beginning of the reversed list
32 // Point current node to whatever dummy is pointing to
33 current.next = dummyNode.next;
34
35 // Make dummy point to the current node (insert at front)
36 dummyNode.next = current;
37
38 // Move to the next node in the original list
39 current = nextNode;
40 }
41
42 // Return the head of the reversed list (node after dummy)
43 return dummyNode.next;
44 }
45}
46
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 /**
14 * Reverses a singly linked list iteratively.
15 * Uses a dummy node to simplify the reversal process.
16 *
17 * @param head The head of the original linked list
18 * @return The head of the reversed linked list
19 */
20 ListNode* reverseList(ListNode* head) {
21 // Create a dummy node to act as the new list's predecessor
22 ListNode* dummyNode = new ListNode();
23
24 // Pointer to traverse the original list
25 ListNode* currentNode = head;
26
27 // Iterate through each node in the original list
28 while (currentNode != nullptr) {
29 // Store the next node before we break the link
30 ListNode* nextNode = currentNode->next;
31
32 // Insert current node at the beginning of the new list
33 // Point current node to what dummy is pointing to
34 currentNode->next = dummyNode->next;
35 // Make dummy point to the current node
36 dummyNode->next = currentNode;
37
38 // Move to the next node in the original list
39 currentNode = nextNode;
40 }
41
42 // Return the head of the reversed list (node after dummy)
43 return dummyNode->next;
44 }
45};
46
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * val: number
5 * next: ListNode | null
6 * constructor(val?: number, next?: ListNode | null) {
7 * this.val = (val===undefined ? 0 : val)
8 * this.next = (next===undefined ? null : next)
9 * }
10 * }
11 */
12
13/**
14 * Reverses a singly linked list iteratively
15 * @param head - The head node of the linked list to reverse
16 * @returns The new head of the reversed linked list
17 */
18function reverseList(head: ListNode | null): ListNode | null {
19 // Handle empty list case
20 if (head === null) {
21 return head;
22 }
23
24 // Initialize pointers for reversal
25 let previousNode: ListNode | null = null; // Tracks the previous node in reversed list
26 let currentNode: ListNode | null = head; // Tracks the current node being processed
27
28 // Iterate through the list and reverse the links
29 while (currentNode !== null) {
30 // Store the next node before we change the current node's next pointer
31 const nextNode: ListNode | null = currentNode.next;
32
33 // Reverse the current node's pointer to point to the previous node
34 currentNode.next = previousNode;
35
36 // Move pointers forward for next iteration
37 previousNode = currentNode;
38 currentNode = nextNode;
39 }
40
41 // Return the new head (which is the last processed node)
42 return previousNode;
43}
44
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the linked list. This is because the algorithm uses a single while loop that traverses through each node in the linked list exactly once. Each operation inside the loop (pointer reassignment) takes constant time O(1)
, and since we visit all n
nodes, the total time complexity is O(n)
.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space regardless of the input size. It creates one dummy node and uses two pointer variables (curr
and next
) to track positions during the reversal process. These variables do not scale with the size of the input linked list, hence the constant space complexity.
Common Pitfalls
1. Losing the reference to the remaining list
One of the most frequent mistakes when reversing a linked list is forgetting to save the reference to the next node before modifying pointers. This leads to losing access to the rest of the original list.
Incorrect approach:
while current:
current.next = dummy_node.next # Oops! We just lost the original current.next
dummy_node.next = current
current = current.next # This now points to dummy_node.next, not the original next node!
This creates an infinite loop or incorrect traversal because current.next
has been modified before we saved its original value.
Correct approach:
while current:
next_node = current.next # Save the reference FIRST
current.next = dummy_node.next
dummy_node.next = current
current = next_node # Use the saved reference
2. Confusing the order of pointer updates
Another common mistake is updating the pointers in the wrong order, which can create cycles or break the list structure.
Incorrect approach:
while current:
next_node = current.next
dummy_node.next = current # Setting this first
current.next = dummy_node.next # Now current.next points to itself!
current = next_node
This creates a self-loop because dummy_node.next
is already pointing to current
when we set current.next = dummy_node.next
.
Correct approach:
while current:
next_node = current.next
current.next = dummy_node.next # First: point current to the existing reversed portion
dummy_node.next = current # Then: make dummy point to current
current = next_node
3. Not handling edge cases properly
Forgetting to handle empty lists or single-node lists can cause issues, though the dummy node approach naturally handles these cases well.
Potential issue without proper initialization:
def reverseList(self, head: ListNode) -> ListNode:
if not head: # Unnecessary check with dummy node approach
return None
dummy_node = ListNode()
# ... rest of code
Better approach (as in the solution): The dummy node approach elegantly handles all edge cases:
- Empty list: The while loop never executes, returns
dummy_node.next
which isNone
- Single node: Loop executes once, correctly returns that single node
- Multiple nodes: Works as expected
The beauty of the dummy node approach is that it treats all cases uniformly without special handling.
Which of the following uses divide and conquer strategy?
Recommended Readings
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!