Reputation:
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
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:
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.
end
references, so you should not need to use them.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
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