Recursion naturally reverses the order of operations. When we recurse to the end of the list first and then print on the way back, we effectively print in reverse. The call stack holds all the nodes, and as each call returns, it prints its node's value.
null, return (base case).head.getNext().Where is the size of the linked list.
A stack provides Last-In-First-Out (LIFO) ordering, which is exactly what we need to reverse the print order. We traverse the list once to push all nodes onto the stack, then pop them one by one to print in reverse order.
stack.head to end, pushing each node onto the stack.stack one by one and print each node's value until the stack is empty.Where is the size of the linked list.
To reduce space complexity, we can divide the list into blocks of size approximately sqrt(n). We only store pointers to the start of each block, requiring O(sqrt(n)) space. For each block, we use recursion to print in reverse. Since each block is small, the recursion depth stays at O(sqrt(n)).
n.block_size as ceil(sqrt(n)).stack.stack and use recursion to print that block in reverse. Limit recursion to the block_size to avoid going into the next block.class Solution:
def printLinkedListInReverseRecursively(self, head: 'ImmutableListNode', size: int) -> None:
if size > 0 and head is not None:
self.printLinkedListInReverseRecursively(head.getNext(), size - 1)
head.printValue()
def getLinkedListSize(self, head: 'ImmutableListNode') -> int:
size = 0
while head is not None:
size += 1
head = head.getNext()
return size
def printLinkedListInReverse(self, head: 'ImmutableListNode') -> None:
linked_list_size = self.getLinkedListSize(head)
block_size = math.ceil(math.sqrt(linked_list_size))
blocks = []
curr = head
for i in range(linked_list_size):
if i % block_size == 0:
blocks.append(curr)
curr = curr.getNext()
while blocks:
self.printLinkedListInReverseRecursively(blocks.pop(), block_size)Where is the size of the linked list.
We can use the slow and fast pointer technique to find the middle of any segment. By recursively printing the second half first, then the first half, we achieve reverse order. The recursion depth is O(log n) because we halve the problem at each level.
start and end pointer (end is exclusive).start is null or equals end, return. If start.getNext() equals end, print start and return.slow and fast pointers to find the midpoint between start and end.slow to end).start to slow).head and null to start.class Solution:
def helper(self, start: 'ImmutableListNode', end: 'ImmutableListNode') -> None:
if start is None or start == end:
return
if start.getNext() == end:
start.printValue()
return
slow = start
fast = start
while fast != end and fast.getNext() != end:
slow = slow.getNext()
fast = fast.getNext().getNext()
self.helper(slow, end)
self.helper(start, slow)
def printLinkedListInReverse(self, head: 'ImmutableListNode') -> None:
self.helper(head, None)Where is the size of the linked list.
Without any extra data structures, we can print in reverse by repeatedly finding the last unprinted node. We maintain an end pointer that marks the boundary. Each iteration traverses from head to find the node just before end, prints it, and moves end backward. This uses constant space but requires O(n) traversals.
end to null (representing the position after the last node).head is not equal to end:head and traverse until finding the node whose next pointer equals end.end to point to this node.Where is the size of the linked list.
The simple recursive solution uses stack space. For very long linked lists, this can cause a stack overflow. Consider the square root decomposition or divide-and-conquer approaches to reduce recursion depth to or respectively.
A common mistake in the recursive approach is calling printValue() before the recursive call, which prints nodes in forward order. The key insight is that the recursive call must happen first, and printing happens after the call returns, so the deepest nodes print first.
When implementing square root decomposition, using integer division for the block size can lead to issues. Use ceil(sqrt(n)) to ensure all nodes are covered. Also, ensure the recursion within each block stops at the block boundary, not at the end of the list.