How do you traverse a singly linked list in reverse using O(1) memory?

Medium
8 years ago

You are given a singly linked list. How do you traverse it in reverse order using O(1) memory? For example, given the list 1 -> 2 -> 3 -> 4 -> null, your traversal should output 4, 3, 2, 1. Explain your approach and provide a code example. Consider the constraints of not modifying the linked list structure itself. Can you adapt your approach if modifying the list structure is permitted, and what would be the trade-offs?

Sample Answer

Reverse Traversal of Singly Linked List in O(1) Memory

Problem Statement

You are given a singly linked list. How do you traverse it in reverse order using O(1) memory? For example, given the list 1 -> 2 -> 3 -> 4 -> null, your traversal should output 4, 3, 2, 1. Explain your approach and provide a code example. Consider the constraints of not modifying the linked list structure itself.

Approach 1: Recursive Traversal (Using Stack Space)

The most straightforward approach to traverse a linked list in reverse is to use recursion. However, this approach uses stack space proportional to the length of the linked list, which does not satisfy the O(1) memory constraint. I'll present it here for comparison.

Code Example (Python)

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None


def reverse_traversal_recursive(head):
    if head is None:
        return
    reverse_traversal_recursive(head.next)
    print(head.data, end=" ")

# Example usage:
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)

node1.next = node2
node2.next = node3
node3.next = node4

print("Reverse traversal (recursive):")
reverse_traversal_recursive(node1)  # Output: 4 3 2 1
print()

Big(O) Analysis (Recursive Approach)

  • Time Complexity: O(n), where n is the number of nodes in the linked list.
  • Space Complexity: O(n) due to the recursive call stack.

Approach 2: Iterative Traversal with Data Storage (Using List)

Another approach is to traverse through the linked list and store each node's data in a list or array. Then, we can iterate through the list in reverse order and print each element. This approach also does not satisfy the O(1) memory constraint as it uses O(n) memory.

Code Example (Python)

def reverse_traversal_iterative_list(head):
    data_list = []
    current = head
    while current:
        data_list.append(current.data)
        current = current.next
    
    for i in range(len(data_list) - 1, -1, -1):
        print(data_list[i], end=" ")


print("Reverse traversal (iterative list):")
reverse_traversal_iterative_list(node1)
print()

Big(O) Analysis (Iterative with List)

  • Time Complexity: O(n), where n is the number of nodes in the linked list.
  • Space Complexity: O(n) due to the data list.

Approach 3: Reversing the Linked List (Modifying the List)

If modifying the linked list structure is permitted, we can reverse the linked list in place, traverse it, and then reverse it back to its original state.

Code Example (Python)

def reverse_linked_list(head):
    prev = None
    current = head
    while(current is not None):
        next_node = current.next
        current.next = prev
        prev = current
        current = next_node
    return prev

def print_linked_list(head):
    current = head
    while(current is not None):
        print(current.data, end=" ")
        current = current.next
    print()


head = node1 # assigning the head of the list for easy reversion later
reversed_head = reverse_linked_list(node1)

print("Reversed traversal (modifying list):")
print_linked_list(reversed_head)

# Reverse it back to its original order:
original_head = reverse_linked_list(reversed_head) #reverse again to keep list in original order
print("Original list (after reverse and reverse back):")
print_linked_list(original_head)

Big(O) Analysis (Reversing the List)

  • Time Complexity: O(n) for reversing and traversing the list.
  • Space Complexity: O(1), as the reversal is done in place.

Trade-offs of Modifying the List Structure

  • Pros: O(1) space complexity during traversal. Simple to implement.
  • Cons: Modifies the original linked list, which might not be desirable in all situations. Requires reversing the list twice, adding to the overall execution time, although still O(n). Not thread-safe if other threads are accessing the list.

Approach 4: Iterative Traversal using a Stack (Simulated on the Heap - Still O(N) Space)

While we want to avoid using extra memory, one approach to consider (though not O(1) space) is manually implementing a stack on the heap using a list. This can simulate the call stack used in recursion without the limitations of the system's call stack, but it still incurs O(N) space complexity.

Approach 5: O(1) Memory Solution - Morris Traversal (Not Recommended for Simple Reverse Traversal)

It's tricky to achieve a true reverse traversal in O(1) space without modifying the list. Morris traversal is a technique that allows you to traverse a tree (or, with adaptation, a linked list) in order without using recursion or a stack, by cleverly modifying the structure of the tree/list temporarily and restoring it afterward.

However, applying Morris traversal for the sole purpose of reverse traversal of a singly linked list is overkill and not recommended in most practical scenarios. The complexity of the algorithm and the potential for errors in its implementation outweigh the benefit of O(1) space, especially since we are only interested in reverse traversal, not in-place reversal.

While theoretically possible, Morris traversal is complex, and modifying the list structure, even temporarily, can introduce risks. The reversing approach is more practical and easier to implement if modification is allowed.

Conclusion

While the ideal O(1) space reverse traversal without modification is challenging for a singly linked list, we've explored several approaches. If modifying the list is allowed, reversing the list is the most practical solution. If modification is strictly prohibited, other methods (like storing to an array, or using recursion) are available, but they have increased memory complexity.