1265. Print Immutable Linked List in Reverse 🔒
Problem Description
You are given the head of an immutable linked list, which means you cannot modify the list structure or node values directly. Your task is to print all values of the linked list nodes in reverse order.
The linked list is accessed through a special interface called ImmutableListNode
that provides only two methods:
printValue()
: Prints the value of the current nodegetNext()
: Returns the next node in the linked list
You cannot access the ImmutableListNode
implementation directly or modify the linked list in any way. You must work exclusively with these two provided methods.
For example, if the linked list contains values 1 -> 2 -> 3 -> 4
, your solution should print the values in the order: 4, 3, 2, 1
.
The key challenge is that you can only traverse the list forward (using getNext()
), but you need to print the values in reverse order. The solution uses recursion to achieve this - it traverses to the end of the list first, then prints values as the recursive calls return, effectively printing from tail to head.
The recursive approach works by:
- Recursively calling the function with the next node until reaching the end of the list (null)
- As the recursive calls return, printing the value of each node
- This ensures that nodes deeper in the list (closer to the tail) are printed before nodes closer to the head
Intuition
When we need to print a linked list in reverse order but can only traverse it forward, we face a fundamental problem: we need to know what comes later before we can print it first. This is like reading a book from the last page to the first, but you can only flip pages forward.
The key insight is that we need to somehow "remember" all the nodes we've seen as we traverse forward, then process them in reverse order. Since we cannot modify the list or store nodes directly, we need a way to implicitly store this information.
Recursion naturally provides this mechanism through the call stack. When we make recursive calls, each function call is pushed onto the stack, creating a natural LIFO (Last In, First Out) structure. This is perfect for our needs because:
- As we traverse forward through the list making recursive calls, we're essentially "storing" each node in the call stack
- When we reach the end (null node), the recursion starts unwinding
- As each recursive call returns, we're processing nodes in reverse order - exactly what we want
The beauty of this approach is that the recursive call self.printLinkedListInReverse(head.getNext())
happens before head.printValue()
. This means we traverse all the way to the end of the list first, building up our call stack, and only then start printing values as we return from each recursive call. The last node reached (the tail) becomes the first to print its value, the second-to-last node prints next, and so on, until finally the head node prints last.
This leverages the natural behavior of recursion to solve our reversal problem without needing any explicit data structures or list modifications.
Learn more about Stack, Recursion, Linked List and Two Pointers patterns.
Solution Approach
The solution implements a recursive approach to print the linked list in reverse order. Let's walk through the implementation step by step:
Base Case:
The function first checks if head
is not null with if head:
. This serves as our base case - when we reach the end of the list (where head
is null), the recursion stops and begins unwinding.
Recursive Step:
self.printLinkedListInReverse(head.getNext())
This line is the core of our recursion. Before doing anything with the current node, we make a recursive call with the next node. This ensures we traverse all the way to the end of the list first.
Processing Step:
head.printValue()
This line executes after the recursive call returns. Due to the nature of recursion, this happens in reverse order - the last node's printValue()
is called first, then the second-to-last, and so on.
Execution Flow Example:
For a list 1 -> 2 -> 3 -> null
, the execution would be:
- Call with node 1: Recursively calls with node 2
- Call with node 2: Recursively calls with node 3
- Call with node 3: Recursively calls with null
- Call with null: Base case hit, returns immediately
- Back in call with node 3: Prints 3
- Back in call with node 2: Prints 2
- Back in call with node 1: Prints 1
The call stack naturally reverses the order of operations. Each recursive call waits for all subsequent calls to complete before executing its printValue()
, achieving the reverse printing without any additional data structures.
Time and Space Complexity:
- Time Complexity:
O(n)
where n is the number of nodes, as we visit each node exactly once - Space Complexity:
O(n)
due to the recursive call stack depth equal to the length of the list
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 a concrete example with a linked list containing values 5 -> 8 -> 2
.
Initial Setup:
- We have an immutable linked list:
5 -> 8 -> 2 -> null
- Our goal is to print:
2, 8, 5
Step-by-Step Execution:
-
First Call - Node 5:
printLinkedListInReverse(node_5)
is called- Check: Is node_5 not null? Yes, continue
- Make recursive call:
printLinkedListInReverse(node_5.getNext())
→ calls with node_8 - (Waiting for recursive call to return before printing 5...)
-
Second Call - Node 8:
printLinkedListInReverse(node_8)
is called- Check: Is node_8 not null? Yes, continue
- Make recursive call:
printLinkedListInReverse(node_8.getNext())
→ calls with node_2 - (Waiting for recursive call to return before printing 8...)
-
Third Call - Node 2:
printLinkedListInReverse(node_2)
is called- Check: Is node_2 not null? Yes, continue
- Make recursive call:
printLinkedListInReverse(node_2.getNext())
→ calls with null - (Waiting for recursive call to return before printing 2...)
-
Fourth Call - Null:
printLinkedListInReverse(null)
is called- Check: Is null not null? No, base case reached
- Function returns immediately without doing anything
-
Unwinding - Back to Node 2:
- The recursive call returns to node_2's context
- Now executes:
node_2.printValue()
→ Prints: 2 - Function returns
-
Unwinding - Back to Node 8:
- The recursive call returns to node_8's context
- Now executes:
node_8.printValue()
→ Prints: 8 - Function returns
-
Unwinding - Back to Node 5:
- The recursive call returns to node_5's context
- Now executes:
node_5.printValue()
→ Prints: 5 - Function completes
Final Output: 2, 8, 5
✓
The key insight is that each node's printValue()
is only called after all nodes that come after it have been processed. The recursion builds up a "stack" of pending print operations, then executes them in reverse order as it unwinds.
Solution Implementation
1# """
2# This is the ImmutableListNode's API interface.
3# You should not implement it, or speculate about its implementation.
4# """
5# class ImmutableListNode:
6# def printValue(self) -> None: # print the value of this node.
7# def getNext(self) -> 'ImmutableListNode': # return the next node.
8
9
10class Solution:
11 def printLinkedListInReverse(self, head: 'ImmutableListNode') -> None:
12 """
13 Print the values of an immutable linked list in reverse order.
14
15 Uses recursion to traverse to the end of the list first,
16 then prints values while backtracking.
17
18 Args:
19 head: The head node of the immutable linked list
20
21 Returns:
22 None (prints values to output)
23 """
24 # Base case: if head is None, we've reached the end
25 if head:
26 # Recursive call: traverse to the next node first
27 self.printLinkedListInReverse(head.getNext())
28
29 # After returning from recursion, print current node's value
30 # This ensures printing happens in reverse order
31 head.printValue()
32
1/**
2 * // This is the ImmutableListNode's API interface.
3 * // You should not implement it, or speculate about its implementation.
4 * interface ImmutableListNode {
5 * public void printValue(); // print the value of this node.
6 * public ImmutableListNode getNext(); // return the next node.
7 * };
8 */
9
10class Solution {
11 /**
12 * Prints the values of a linked list in reverse order.
13 * Uses recursion to traverse to the end of the list first,
14 * then prints values while backtracking.
15 *
16 * @param head The head node of the immutable linked list
17 */
18 public void printLinkedListInReverse(ImmutableListNode head) {
19 // Base case: if head is null, return without doing anything
20 if (head != null) {
21 // Recursive call: first traverse to the next node
22 printLinkedListInReverse(head.getNext());
23
24 // After returning from recursion, print current node's value
25 // This ensures printing happens in reverse order
26 head.printValue();
27 }
28 }
29}
30
1/**
2 * // This is the ImmutableListNode's API interface.
3 * // You should not implement it, or speculate about its implementation.
4 * class ImmutableListNode {
5 * public:
6 * void printValue(); // print the value of the node.
7 * ImmutableListNode* getNext(); // return the next node.
8 * };
9 */
10
11class Solution {
12public:
13 /**
14 * Prints the values of an immutable linked list in reverse order.
15 * Uses recursion to traverse to the end of the list first,
16 * then prints values while backtracking.
17 *
18 * @param head Pointer to the first node of the immutable linked list
19 */
20 void printLinkedListInReverse(ImmutableListNode* head) {
21 // Base case: if head is null, return immediately
22 if (head == nullptr) {
23 return;
24 }
25
26 // Recursive case: first traverse to the next node
27 printLinkedListInReverse(head->getNext());
28
29 // After returning from recursion, print current node's value
30 // This ensures printing happens in reverse order
31 head->printValue();
32 }
33};
34
1/**
2 * // This is the ImmutableListNode's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * class ImmutableListNode {
5 * printValue() {}
6 *
7 * getNext(): ImmutableListNode {}
8 * }
9 */
10
11/**
12 * Prints the values of a linked list in reverse order using recursion.
13 *
14 * The function traverses to the end of the list first, then prints values
15 * as the recursive calls return, achieving reverse order printing.
16 *
17 * @param head - The head node of the immutable linked list
18 */
19function printLinkedListInReverse(head: ImmutableListNode): void {
20 // Base case: if head is null or undefined, stop recursion
21 if (head) {
22 // Recursive call: traverse to the next node first
23 printLinkedListInReverse(head.getNext());
24
25 // Print current node's value after returning from recursive call
26 // This ensures values are printed in reverse order
27 head.printValue();
28 }
29}
30
Time and Space Complexity
Time Complexity: O(n)
The recursive function visits each node in the linked list exactly once. Starting from the head, it traverses to the end of the list through recursive calls to head.getNext()
, then prints each node's value as the recursion unwinds. Since we perform a constant amount of work (calling getNext()
and printValue()
) for each of the n
nodes, the total time complexity is O(n)
.
Space Complexity: O(n)
The space complexity is determined by the recursive call stack. Since the function recursively calls itself for each node before printing any values, the maximum depth of the recursion equals the length of the linked list. Each recursive call adds a new frame to the call stack, storing the local variable head
and the return address. With n
nodes in the list, we have n
recursive calls simultaneously on the stack before the first printValue()
is executed, resulting in O(n)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Stack Overflow for Large Lists
The recursive solution uses O(n) stack space, which can cause a stack overflow error for very large linked lists (typically lists with thousands of nodes, depending on the system's stack size limit).
Why it happens: Each recursive call adds a new frame to the call stack. For a list with 10,000 nodes, you'll have 10,000 recursive calls stacked up before any printing begins.
Solution - Iterative Approach with Explicit Stack:
class Solution:
def printLinkedListInReverse(self, head: 'ImmutableListNode') -> None:
# Use an explicit stack to store nodes
stack = []
current = head
# Traverse the list and push all nodes to stack
while current:
stack.append(current)
current = current.getNext()
# Pop from stack and print (achieves reverse order)
while stack:
stack.pop().printValue()
2. Missing Base Case Check
A common mistake is forgetting to check if head
is None before making recursive calls, leading to AttributeError when trying to call methods on a None object.
Incorrect Implementation:
def printLinkedListInReverse(self, head: 'ImmutableListNode') -> None:
# Missing null check - will crash when head is None
self.printLinkedListInReverse(head.getNext()) # Error if head is None
head.printValue() # Error if head is None
Correct Implementation: Always check if the node exists before accessing its methods:
def printLinkedListInReverse(self, head: 'ImmutableListNode') -> None:
if head: # Essential null check
self.printLinkedListInReverse(head.getNext())
head.printValue()
3. Attempting to Modify the List
Since the list is immutable, trying to reverse it in-place or modify node connections will fail. Some might instinctively try approaches like:
Wrong Approach:
# This won't work - can't modify immutable nodes
prev = None
current = head
while current:
next_node = current.getNext()
current.next = prev # Can't do this - no setter available
prev = current
current = next_node
The immutable nature forces us to use either recursion or auxiliary data structures to achieve the reverse printing without modifying the original list structure.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
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
Want a Structured Path to Master System Design Too? Don’t Miss This!