Skip to content

How to Reverse a Linked List: A Comprehensive Guide with Code Examples

Reversing a linked list means flipping the order of elements so the tail node becomes the new head. This guide covers everything from use cases to iterative and recursive implementations in Python.

Overview

In this extensive walkthrough, we’ll learn:

  • What is a reversed linked list – Definition and visualization
  • Why reverse a linked list – Print in reverse, modify end nodes, process recursively
  • Iterative approach – Using prev, current, next node trackers
  • Recursive approach – Dividing into sublists and reversing
  • Doubly and circular linked lists – Modifications for variants
  • Python code examples – Each method implemented clearly
  • Complexity analysis – Time and space big O compared
  • Testing tips – Edge cases and gotchas
  • FAQs – Key questions answered

Whether you’re preparing for interviews or working on a coding project leveraging linked lists, understanding reversal techniques is crucial. Let’s dive in!

What is a Reversed Linked List?

A linked list is a foundational data structure consisting of a sequence of nodes that store values and connections to other nodes:

Linked list diagram

  • Nodes contain a data value and next pointer
  • The head node starts out the list
  • The tail marks the end with a null next pointer

To traverse a typical linked list, you‘d follow the next pointers from the head to the tail.

However, in a reversed linked list, the order of nodes is inverted:

Reversed linked list

Now, the tail becomes the new head, and head becomes the new tail. The next pointers are effectively reversed.

For example, reversing the list:

A → B → C → D

Would result in:

D → C → B → A

The sequence is flipped while preserving underlying data at each node.

But why would we want to reverse instead of creating lists in reverse order already? Excellent question, let‘s discuss some use cases next.

Why Reverse a Linked List?

Here are five compelling examples of when reversing a linked list becomes useful:

1. Print Elements in Reverse Order

Since nodes sequence backwards after reversal, you can easily print in reverse by traversing from head to tail:

Given List: A → B → C → D
Print Normal:  A, B, C, D  
Print Reversed: D, C, B, A

2. Deleting Nodes Near the End

Say you had a million node list, and wanted to delete the last 10 nodes.

Without reversing, you‘d need to traverse 999,990 nodes first to reach them – very slow!

But reverse the list, and they move to the head for fast deletion in the first 10 nodes.

3. Recursively Process Nodes Backwards

Certain recursive algorithms (like some machine learning models) require processing linked list data in reverse.

Reversing nodes allows feeding data backwards.

4. Check Palindrome Linked Lists

A palindrome reads the same backwards and forwards (e.g. ‘Racecar‘).

Efficiently verifying palindrome linked lists involves:

  1. Reversing contents
  2. Comparing reversed to original
  3. If identical, it‘s palindrome

5. Optimize Stack/Queue Usage

Reversed segments can help balanced push/pop linked list operations resembling a stack or queue data structure.

The key insight is that reversing enables alternative traversal directions.

This unlocks niche capabilities compared to creating already-reversed lists upfront.

Now let‘s explore actually implementing reversal.

How to Reverse a Linked List

We‘ll focus on two primary techniques:

  1. Iterative method
  2. Recursive method

Let‘s explore each approach including Python code samples.

Iterative Approach

The iterative technique uses a while loop to traverse the list, swapping next pointers as it goes until null.

We‘ll need to track three key nodes:

  1. prev – Previous node
  2. current – Current node being processed
  3. next – Next node in the sequence

Then the logic works as follows:

  1. Initialize prev as null, current as the head
  2. Save next pointing to node after current
  3. Reverse current.next to point to prev instead
  4. Traverse: set prev=current, current=next
  5. Repeat steps 2-4 until we reach null terminator

Let‘s see this implemented in Python:

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

def reverse_iter(head):

  prev, current = None, head

  while current:

    next_node = current.next  # Temp save next

    current.next = prev # Reverse pointer

    prev, current = current, next_node  

  return prev

We define a Node class holding data and a next reference.

The reverse_iter() method takes the original head node as input:

  • Initialize prev as None, current as head
  • Traverse with while loop until current == None
  • Before reversing pointer, temporarily save next_node
  • Reverse current.next to point to prev
  • Advance prev and current forward

Eventually we hit the null tail pointer, return prev as new head.

By saving next_node before reversing pointers, we prevent losing connections.

Now let‘s contrast with a recursive approach.

Recursive Approach

Recursive solutions apply the reversal recursively reducing linked list size, then stitch results:

  1. Break list into head and sublist after head
  2. Call reverse() recursively on sublist
  3. Append head to end of return reversed sublist
  4. Repeat until base case of 0/1 node

Implemented in Python:

def reverse_recur(head, prev=None):

  if not head: 
    return prev # Base case

  next_node = head.next
  head.next = prev # Reverse pointer

  return reverse_recur(next_node, head)  

def reverse(head):
  return reverse_recur(head)  
  • Base case: Return prev when head == None
  • prev links returned reversed sublists
  • next_node progresses recursion down original list

By dividing into head + remainder sublist, we eventually hit base cases reducing list size each call.

Let‘s now contrast iterative vs recursive complexity.

Comparing Complexity Tradeoffs

How do these two approaches compare regarding efficiency?

We‘ll analyze both time and space complexity.

Time Complexity

  • Both solutions are O(N) linear time, processing each node once
  • No clear speed advantage comparing iterative vs recursive

Space Complexity

  • Iterative uses O(1) constant space with two node pointers
  • Recursive requires O(N) stack space proportional to N calls

Takeaway: iterative solutions are typically more memory efficient.

Opt for recursive only if needed or memory issues are negligible.

Now let‘s extend reversal to linked list variants.

Reversing Doubly & Circularly Linked Lists

So far we focused on singly linked lists with just next references.

But let‘s discuss adaptations for:

  1. Doubly linked lists
  2. Circular linked lists

Doubly Linked Lists

In a doubly linked list, each node stores both next and previous pointers:

Doubly linked list

Why both next and previous? Enables traversal in either direction!

To reverse a doubly linked list:

Simply reverse BOTH next and prev pointers:

node.next, node.prev = node.prev, node.next

By correctly flipping the orientation of both connection types, you fully reverse directionality retaining doubly linked structure.

Circular Linked Lists

A circular linked list connects tail back to the head in a continuous loop:

Circular linked list

To reverse:

  1. Break circle by setting tail.next = None
  2. Reverse entire list normally
  3. Relink circle by setting new tail.next = new head

This way we reverse the sequence order while preserving circular connectivity.

Next let‘s discuss testing reversed lists effectively.

Testing Reversed Linked Lists

When assessing reversal logic, focusing testing on edge cases and key validation checks leads to robust solutions:

Edge Cases

  • Empty list – immediately return input head
  • Single node – no change
  • Two nodes – flip pointers

Validation

  • New head equals former tail node data
  • New tail equals former head
  • Compare expected vs actual node sequences

For example test cases:

Input List A → B → C → D Reversed Output D → C → B → A
Empty Input [] Return input head immediately Single Node [A] No change
Two Nodes [A, B] Flip pointers Circular A→B→A Connect D → A after reversing

Get both happy path and edge scenarios covered!

Let‘s wrap up with some key takeaways.

Summary and Key Lessons

We explored quite a bit on reversing linked lists – let‘s recap core concepts:

  • Reversing inverts order where tail becomes head, head becomes tail
  • Use cases include reverse order printing, faster deletions, supporting recursive algorithms
  • Iterative solutions tend to be more space efficient
  • Recursive costs more memory with O(N) call stack
  • To handle doubly linked lists, reverse both next and prev
  • For circular, break circle first before standard reversal

I hope these examples give you an idea of how to approach reversal techniques even beyond interview trivia into practical coding applications.

Next I‘ll leave you with frequently asked questions to solidify understanding.

Frequently Asked Questions

Q: What is the time complexity of reversing a linked list?

Both the iterative and recursive solutions covered have a linear time complexity of O(N) since each node gets processed exactly once.

Q: How much extra memory does the recursive approach require?

The recursive algorithm requires O(N) stack space proportional to the number of recursive calls for a list of N nodes. Compared to using constant O(1) extra memory in iteration.

Q: When would the recursive solution be preferred?

If memory usage is not a major constraint and you specifically need recursion, the elegance of the recursive solution may make it appealing. But in most cases, iteration would be more optimal engineering.

Q: What adaptations are required for circular linked list reversal?

To reverse a circular linked list, you need to first break the circular connection to prevent endless looping. Set the tail.next = None, reverse the list normally, then reconnect the new tail to new head.

I hope these examples give you a very thorough overview of techniques and considerations when needing to reverse a linked list in practice! Please reach out with any other questions.