user15740803
user15740803

Reputation:

How does this recursive function to reverse a linked list work?

I found the function below that reverses a linked list recursively:

def recursive(self, head, end):
    if not head:
        return None, None
    if head.next == end:
        head.next = None
        return head, head
    newHead, newEnd = self.recursive(head.next, end)
    newEnd.next = head
    head.next = None
    return newHead, head

I understand the if statements that cover for the base case.

But I do not understand the recurrence relation.

How does that recursion work to reverse the list? Is there a more simple recursive version that reverses a linked list? For reference, I am solving LeetCode problem 206. Reverse Linked List:

Given the head of a singly linked list, reverse the list, and return the reversed list.

Upvotes: 0

Views: 453

Answers (2)

trincot
trincot

Reputation: 350771

I do not understand the recurrence relation.

Let's say we have this linked list:

 head                                                  end
  ↓                                                     ↓
┌───────────┐    ┌───────────┐                        ┌───────────┐
│ value: 85 │    │ value: 15 │                        │ value: 20 │
│ next: ———————→ │ next: ———————→ ...more nodes...——→ │ next:null │
└───────────┘    └───────────┘                        └───────────┘ 

The recursive part is based on the following observation:

If you can reverse a list that is one element shorter, which excludes the current head node, then we should arrive in a situation like this:

 head                                                  end
  ↓                                                     ↓
┌───────────┐    ┌───────────┐                        ┌───────────┐
│ value: 85 │    │ value: 15 │                        │ value: 20 │
│ next: ———————→ │           │                        │           │
│           │    │ next:null │ ←——...more nodes...←———————— :next │ 
└───────────┘    └───────────┘                        └───────────┘ 
                   ↑                                    ↑
                  newEnd                               newHead

At this stage we do not question how it did that. We just assume it works for the recursive case. So if it works correctly, we should get the above state of the list.

Now the remaining statements will link the current head node so that it finishes the reversal job for a list that includes this node also:

newEnd.next = head

This produces this state:

 head                                                  end
  ↓                                                     ↓
┌───────────┐    ┌───────────┐                        ┌───────────┐
│ value: 85 │    │ value: 15 │                        │ value: 20 │
│ next: ———————→ │           │                        │           │
│           │ ←——————— :next │ ←——...more nodes...←———————— :next │ 
└───────────┘    └───────────┘                        └───────────┘ 
                   ↑                                    ↑
                  newEnd                               newHead

Then we execute:

head.next = None

These two assignments have made the current head a tail node to the reversed list we got back from recursion:

 head                                                  end
  ↓                                                     ↓
┌───────────┐    ┌───────────┐                        ┌───────────┐
│ value: 85 │    │ value: 15 │                        │ value: 20 │
│ next:null │ ←——————— :next │ ←——...more nodes...←———————— :next │ 
└───────────┘    └───────────┘                        └───────────┘ 
                   ↑                                    ↑
                  newEnd                               newHead

Now we just need to tell the caller which is the head and tail node of this reversed list:

return newHead, head

When you look at the final state, you see indeed that those are the head and tail of the reversed list.

So, now, we know that:

  • The base case works (you were already clear on that)
  • The recursive case works on the condition that recursion correctly returns a reversed list for the list that excludes the first node

By induction you can then see that if it works for a list with just one node, it also works for a list with 2, with 3, ...etc.

Remarks

  • The LeetCode problem you linked to does not use end references, so you should not need to use them.
  • Recursion has its limits. When the list is long, you could run into a stack overflow exception.

An iterative method is to keep a reference of the preceding node while you walk along the list and relink each next reference. Here is how that works for the LeetCode challenge (no end reference):

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        prev = None
        while head:
            head.next, prev, head = prev, head, head.next
        return prev

Upvotes: 1

Booboo
Booboo

Reputation: 44283

Actually, I don't understand that function. First, you should only need to be passing to such a function the head of the list; I don't know why argument end is being passed or why it even needs to be passed. Also, argument self suggests that this is a method that is part of a class definition that is not being shown. Let's start anew:

Here is a simple class for defining a node that can be linked to another node and be tagged with a "name" for identification. We also include a small function to walk the linked list to print out the nodes:

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

a = Node('A')
b = Node('B')
c = Node('C')
d = Node('D')
e = Node('E')
a.next = b
b.next = c
c.next = d
d.next = e

def print_list(head):
    while head:
        print(head.name)
        head = head.next

print_list(a)

Prints:

A
B
C
D
E

Next we present the recursive function to reverse such a list. This function is passed the head node of the list, asserted to be not None. The rest I have tried to explain as comments at each step of the algorithm. A key point is comment 7: make what we had been pointing to now point back to us. This relates to comment 5: we now point backwards to None in that when we set head.next = None it will only stay None if this node was the head node of the entire list. Otherwise, per comment 7, it will later on be updated to point back to a previous node.

def reverse(head):
    assert(head) # 1. make sure head is not None
    next_head = head.next # 2. get the next node, if any
    if next_head is None: # 3. if we do not point to a next node, then
        return head # 4. just return this node as the new head
    head.next = None # 5. we now point backwards to None
    # 6. otherwise, reverse recursively what we were pointing to and get the new head
    new_head = reverse(next_head)
    # 7. make what we had been pointing to now point back to us:
    next_head.next = head
    # 8. and return the new head
    return new_head

# without comments for clarity:
def reverse_no_comments(head):
    assert(head)
    next_head = head.next
    if next_head is None:
        return head
    head.next = None
    new_head = reverse(next_head)
    next_head.next = head
    return new_head

def print_list(head):
    while head:
        print(head.name)
        head = head.next
        

print('Reversed:')
new_head = reverse(a)
print_list(new_head)

Prints:

Reversed:
E
D
C
B
A

Upvotes: 0

Related Questions