Linked List reversal algorithm not working - python

I'm trying to write a simple function that reverses a linked list in Python:
def reverse(head):
prev = None
nxt = head.next
head.next = prev
while nxt:
prev = head
head = nxt
head.next = prev
nxt = nxt.next
return head
The logic seemed fine when I thought through it.:
First move prev to head, then shuffle head forward so it points to the same node as nxt, before setting head.next to where it was. Finally moving nxt forward by one. Iterate until nxt reaches the end of the list.
However when I tried to reverse a linked list 0->1->2->3->4->5 with the following code:
def traverse(head):
while head:
print(head.val)
head = head.next
it repeatedly printed 0 and 1 endlessly.

As Michael pointed out, your code makes first and the second element in the linked list to point to each other.
The fix is pretty simple, here is what I did:
def reverse(head):
nxt = head.next
head.next = None
while nxt:
temp1 = nxt.next
temp2 = head
head = nxt
nxt.next = temp2
nxt = temp1
This code initially initially does the same thing as yours. The while loop however, uses 2 temporary variables temp1 and temp2 to hold the addresses of the variables surrounding nxt (on either sides). This prevents the conflict that happened in your code.
Hope this helps!
Cheers!

In the while-loop:
head = nxt # head and nxt now refer to the same node
head.next = prev # head.next now points to previous node but nxt.next does as well
nxt = nxt.next # this is same as: nxt = head.next therefore same as: nxt = prev

Related

understand head and pointers for a singly-linked list

Maybe this is a rather basic question, but I have seem this quite a lot on leetcode.
For example, one provides me a head of a singly linked list, than I define a pointer variable say:
curr = head
After I revert the head:
def revert(head):
prev, curr = None, head
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
return prev
Now I would obtain a singly linked list, in which case the order is reversed and prev is the head of it.
When the loop completes, of course the curr pointer points to None, I am wondering why the head gets modified to only include the last element.
And a more generic question is that, how come does the head gets updated automatically once the pointer is changed? Is this something that is achieved by the leetcode backend or something that is aware from a data structure perspective.
how come does the head gets updated automatically once the pointer is changed?
The short answer is that it doesn't get updated automatically.
In your example, the variable head points to a mutable object, meaning that modifications to it happen in place even when done from a function scope.
A regular python list is also mutable, to demonstrate let's take a regular python list and make some modifications to the contents.
def change_last_index(example):
lst = example # lst and example both point to the same mutable object
lst[-1] = 153
example = [1, 2, 3]
change_last_index(example)
print(example) # [1, 2, 153]
It's the same concept with the links/nodes of the linked list.
Lets create an example Node class/data structure that works with your function.
class Node:
def __init__(self, val):
self.val = val
self.next = None
def __repr__(self):
return f"{self.val} -> {self.next}"
And to demonstrate it's mutability we can create a function that creates a new node and assigns it to it's next attribute.
def set_next_node(node, value):
node1 = node # node1, node, and head all point to same object
node1.next = Node(value) # create new node and assign to head.next
head = Node(0)
print(head) # output: 0 -> None
set_next_node(head, 1)
# the function will create a new node with value of 1 set it to `head.next`
print(head) # output: 0 -> 1 -> None
In your revert function on the first line...
prev, curr = None, head
... the curr variable now points to the same object that the head points. So on the very first iteration of the while loop, head.next gets changed to None when the line curr.next = prev gets executed.
Then on the second iteration the very same line assigns head to the next attribute of a different node because prev gets reasigned to the head object when the line prev = curr gets executed.
Here is an example you can run using your revert function and the example Node class from above. I suggest running it and all the above code in an IDE/debugger so you can track the changes step by step, and run your own experiments, like making the linked list longer or changing the node values to strings.
head = Node(0)
head.next = Node(1)
head.next.next = Node(2)
print(head) # 0 -> 1 -> 2 -> None
def revert(head):
prev, curr = None, head
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
return prev
result = revert(head)
print(head) # 0 -> None
print(result) # 2 -> 1 -> 0 -> None
Hopefully you can see that nothing is happening automatically. All the modifications to head and all other elements of the linked list are direct results of the steps taken in your function.

Question about Linked List variable assignments

When reversing a singly linked-list, I am running into some trouble with variables changing.
def reverseList(self, head: ListNode) -> ListNode:
current = head
prev = None
while head:
current.next = prev # why does this line set head equal to [1] on the first iteration?
prev = current
head = head.next
current = head
return prev
The only time I change head is when assigning head = head.next. Changing current.next shouldn't affect head. So why does the head variable change on the commented line?

How to return the middle node of a linked list

I'm confused about how the while loop condition works in a linked list when checking for the middle node of a linked list
This is the correct code that I have for finding the middle node of a linked list
class Node(object):
def __init__(self, data):
self.data = data
self.next = None
class linkedList(object):
def __init__(self):
self.head = None
def append(self, data):
node = Node(data)
if self.head == None:
self.head = node
temp = self.head
while temp.next:
temp = temp.next
temp.next = node
def middle(self):
first = self.head
second = self.head
while second and second.next:
second = second.next.next
first = first.next
print(first.data)
If I change the while loop to
while second:
or
while second.next:
I receive an error that says
AttributeError: 'NoneType' object has no attribute 'next' on line 24
I'm just wondering why it's important to have both second and second.next
The solution works by using two pointers. First one takes 1 step at a time and second 2 steps at a time. However, in taking 2 steps there are two things to verify:
There is a next step that is valid
There is a step after the next step mentioned above
If you skip one check it will enter the loop but at the boundary condition it not find the next. To illustrate: If there are 4 nodes and you only check for second.next at the 3nd node you'll have second.next as valid and you'll enter the while loop but inside that you are directly accessing second.next.next
F,S
|
1 --> 2 --> 3 --> 4 --> None
For starters, your append method doesn't work, and gets stuck in an infinite while loop, since you don't exit out of append on adding the first element. The correct version is
def append(self, data):
node = Node(data)
if self.head == None:
self.head = node
return
else:
temp = self.head
while temp.next:
temp = temp.next
temp.next = node
As to your other question, we want to find the middle of the loop for both even and odd lists, the second.next covers the odd list case, and the second covers the even list case, since either the 2nd pointer will point to null, or it will be null itself, and if you use only one of them, you will get the error you described it, hence you need to have both conditions in the while loop

Approach on Reversing a Linked List in Python

I tried the following code to reverse a linked list and is getting an infinite loop as an error. Can you pls tell me what's wrong in this approach.
def reverse(self):
temp = curr = self.head #curr refers to the next node
prev = None
while temp:
curr = temp.next #curr goes to the next node of temp
curr.next = temp #curr node points to its previous node temp
prev = temp #prev moves to the next node
temp = curr
#self.head.next = None
self.head = prev
There is a logic error in your method.
At the end of the first pass of the while loop:
curr (2nd element in list)
curr.next (1st element in list)
temp = curr = (2nd element in list)
In the second pass of the while loop. You expect to reach the 3rd element using temp.next. This is wrong because:
temp.next = curr.next = (1st element in list)
Leaving you to loop infinitely between the first and second element with no exit condition.
I will leave you to figure out the proper solution for this.
(Hint: temp should be assigned to the ??? element in the 1st pass)

Removing the lastNode in a singly Linked List

Can I check
how do we remove a last node from a single linked list?
is it the same as how we remove the first node?
remove first Node
def deleteAtHead(self):
temp = self.head
self.head = self.head.next
delete temp
remove last Node
def deleteAtTail(self):
prev = None
temp = self.tail
self.tail= self.tail.prev
delete temp
You have to crawl back to the tail starting at the head.
The tail is the first node with no next: next is None.
Keeping track of the next-to-last (prev), you set its next to None.
def deleteAtTail(self): # remove_last would likely be a better name
""" removes the last element of the singly linked list
"""
temp = self.head
while(temp.next is not None):
prev = temp
temp = temp.next
prev.next = None
Setting prev.next to None removes the tail node (it will be garbage collected if there are no other references to it)
You cannot do remove operation in single-linked list with O(n) = c complexity because you don't have reference for previous list node. You will have to traverse to the end of the list remembering current and previous nodes and then you will have to trim previous node from the reference to it's next, which is the last node you are trying to remove. This will always have O(n) = n complexity for single-linked lists.
Because this is a singly linked list you will have to iterate through the list to get to the node just before tail. I think your function would simply need to be
def deleteAtTail(self):
temp = self.head
while(temp.next != None):
if temp.next == tail: #finds the node just before tail
break
temp = temp.next
temp.next = None
self.tail = temp
We set self.tail = temp because temp is equal to the node just before the end of the list. So we delete temp (by removing its last reference) and then set tail equal to node we earlier identified as the second to last node in the list as it will in fact be the new end of the list.
EDIT:
To address the concerns of some of the comments on this answer. It is good practice to maintain a head, tail and current pointer when dealing with a singly or doubly linked list. Knowing where tail is located is extremely valuable for making insertions into your list something that is computationally reasonable.
This means that your insertion method can go from this:
def insert(value):
temp = self.head
while(temp.next != None):
temp = temp.next
temp.next = Node()
temp.next.value = value
which has a time complexity O(n) to this:
def insert(value):
self.tail.next = Node()
self.tail.next.value = value
self.tail = self.tail.next
which is has a substantially more favorable time complexity of O(1).

Categories

Resources