Reverse a singly linked list - python-3.x

Reverse a singly linked list.
Example:
Input: 1->2->3->4->5->NULL
Output: 5->4->3->2->1->NULL
Here's what I saw online:
class Solution:
def reverseList(self, head):
def reverse(prev, cur):
if cur:
reverse(cur, cur.next)
cur.next = prev
else:
nonlocal head
head = prev
reverse(None, head)
return head
But I didn't get the process how it works after the if cur.
For example the test case is [1,2,3,4,5].
input None, 1 into the reverse function
cur exist, run reverse function with 1, 2
...
cur exist, run reverse function with 4, 5
cur does not exist, (then what???)
BTW why does listNode even exist? It is not as easy as others like list array or dictionary. When should i use it?

There's no efficient way to do this. The only way without interacting with the list beforehand, is an algorithm of a complexity of O(n * n), where you repeatedly go through the linked list, find last element and remove it, while adding it to a new linked list, until the first list is completely empty. If you wanted to preserve that list, you'd have to recreate that list during the reversing or simply copy it beforehand, not fun at all. This is what example you posted does.
def reverse(prev, cur):
if cur: # this should actually be if cur is not None:
reverse(cur, cur.next) # call same method on next node
cur.next = prev # when we are back from recursion hell, set previous one as next one, because we are reversing
else: # we finally reached the end
nonlocal head # excuse me, this is in a class but you wrap in multiple functions and use nonlocal??? Where did you even dig out this thing from?
head = prev # head now becomes the previous node, because this node is None, or, end of the list
If you just want to learn about them, then this algorithm is perfectly fine, in any other case, you should at least upgrade to doubly linked list, you'll use insignificant amount of memory to speed up reversal a lot.
If you create your own doubly linked list, reversing could be as fast as swapping end and start "pointers", if you write it in a way where every node has two references, left and right in a list, such as neighbours = [left, right], your linked list could store a value called advance direction, which would be either 0 or 1, meaning that you could reverse entire list from start to end while going to left to right to end to start while going right to left by merely swapping start with end and changing "advance value" from 1 to 0. The advance value would be wrapped in a method like next(), which would look something like this:
def next(self):
if self.current is None:
return None # could be return self.current or even just "return", this is Python
self.current = self.current.neighbours[self.advance_value]
return self.current
When advance_value is 0, it goes left, when advance_value is 1, it goes right through the list, all elements stay in their place, but to whoever is accessing it, it looks like it's reversed.

Related

Palindrome problem - Trying to check 2 lists for equality python3.9

I'm writing a program to check if a given user input is a palindrome or not. if it is the program should print "Yes", if not "no". I realize that this program is entirely too complex since I actually only needed to check the whole word using the reversed() function, but I ended up making it quite complex by splitting the word into two lists and then checking the lists against each other.
Despite that, I'm not clear why the last conditional isn't returning the expected "Yes" when I pass it "racecar" as an input. When I print the lists in line 23 and 24, I get two lists that are identical, but then when I compare them in the conditional, I always get "No" meaning they are not equal to each other. can anyone explain why this is? I've tried to convert the lists to strings but no luck.
def odd_or_even(a): # function for determining if odd or even
if len(a) % 2 == 0:
return True
else:
return False
the_string = input("How about a word?\n")
x = int(len(the_string))
odd_or_even(the_string) # find out if the word has an odd or an even number of characters
if odd_or_even(the_string) == True: # if even
for i in range(x):
first_half = the_string[0:int((x/2))] #create a list with part 1
second_half = the_string[(x-(int((x/2)))):x] #create a list with part 2
else: #if odd
for i in range(x):
first_half = the_string[:(int((x-1)/2))] #create a list with part 1 without the middle index
second_half = the_string[int(int(x-1)/2)+1:] #create a list with part 2 without the middle index
print(list(reversed(second_half)))
print(list(first_half))
if first_half == reversed(second_half): ##### NOT WORKING BUT DONT KNOW WHY #####
print("Yes")
else:
print("No")
Despite your comments first_half and second_half are substrings of your input, not lists. When you print them out, you're converting them to lists, but in the comparison, you do not convert first_half or reversed(second_half). Thus you are comparing a string to an iterator (returned by reversed), which will always be false.
So a basic fix is to do the conversion for the if, just like you did when printing the lists out:
if list(first_half) == list(reversed(second_half)):
A better fix might be to compare as strings, by making one of the slices use a step of -1, so you don't need to use reversed. Try second_half = the_string[-1:x//2:-1] (or similar, you probably need to tweak either the even or odd case by one). Or you could use the "alien smiley" slice to reverse the string after you slice it out of the input: second_half = second_half[::-1].
There are a few other oddities in your code, like your for i in range(x) loop that overwrites all of its results except the last one. Just use x - 1 in the slicing code and you don't need that loop at all. You're also calling int a lot more often than you need to (if you used // instead of /, you could get rid of literally all of the int calls).

Original linked list and its copy, both are getting updated simultaneously when I am making changes only to one?

I am trying to solve LeetCode problem 19. Remove Nth Node From End of List:
Given the head of a linked list, remove the nth node from the end of the list and return its head.
Here is my solution.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
Length=1
curr=head
while curr.next!=None:
Length=Length+1
curr=curr.next
count = 0
curr=head
if Length == n:
return head.next
while count < Length-n:
if count == Length-n-1:
curr.next = curr.next.next
break
count = count+1
curr = curr.next
return head
My question is, when I am making all the changes to curr (copy of the original), why are those changes getting reflected in the head (original) as well?
The thing is that curr is not a copy of the original (node or list). It is a name you give for a given node that is in the original list. As long as you don't really create new nodes (calling Node(value)), you cannot be referencing anything else than the nodes in the original list. curr references one node after the other in the original list and at a certain point mutates a next reference. This happens in the original list. There is no other list at play.
The only time that the caller does not see a change in the original list, is when you don't return head, but head.next. In that case head still refers to the original list, while the returned node reference, is skipping the first node. But both lists have all nodes in common except the node referenced by head.

Sorting algoritm

I want to make my algorithm more efficient via deleting the items it already sorted, but i don't know how I can do it efficiently. The only way I found was to rewrite the whole list.
l = [] #Here you put your list
sl = [] # this is to store the list when it is sorted
a = 0 # variable to store which numbers he already looked for
while True: # loop
if len(sl) == len(l): #if their size is matching it will stop
print(sl) # print the sorted list
break
a = a + 1
if a in l: # check if it is in list
sl.append(a) # add to sorted list
#here i want it to be deleted from the list.
The variable a is a little awkward. It starts at 0 and increments 1 by 1 until it matches elements from the list l
Imagine if l = [1000000, 1200000, -34]. Then your algorithm will first run for 1000000 iterations without doing anything, just incrementing a from 0 to 1000000. Then it will append 1000000 to sl. Then it will run again 200000 iterations without doing anything, just incrementing a from 1000000 to 1200000.
And then it will keep incrementing a looking for the number -34, which is below zero...
I understand the idea behind your variable a is to select the elements from l in order, starting from the smallest element. There is a function that does that: it's called min(). Try using that function to select the smallest element from l, and append that element to sl. Then delete this element from l; otherwise, the next call to min() will select the same element again instead of selecting the next smallest element.
Note that min() has a disadvantage: it returns the value of the smallest element, but not its position in the list. So it's not completely obvious how to delete the element from l after you've found it with min(). An alternative is to write your own function that returns both the element, and its position. You can do that with one loop: in the following piece of code, i refers to a position in the list (0 is the position of the first element, 1 the position of the second, etc) and a refers to the value of that element. I left blanks and you have to figure out how to select the position and value of the smallest element in the list.
....
for i, a in enumerate(l):
if ...:
...
...
If you managed to do all this, congratulations! You have implemented "selection sort". It's a well-known sorting algorithm. It is one of the simplest. There exist many other sorting algorithms.

TypeError: list indices must be integers or slices, not str. While selecting item from loop for

I´ve been trying for a while to select an item from a list with the variable of the for loop. But I keep getting this error:
TypeError: list indices must be integers or slices, not str
The issue dissapears when I change the i for a number, but that's not what I want to do. I´ve been looking for similar issues but couldn't manage to get it working. Advise please.
I want this to result as: ['p1', 'q1', 'p2', 'q2', 'p3', 'q3', 'p4', 'q4', 'p5', 'q5']
listcont=[]
cont=0
while cont<=5:
for i in list:
listcont.append(list[i]+str(cont))
cont+=1
return listcont
n=5
list=['q','p']
print(concat(list,n))´´´
First, when you write for i in list you're already iterating over the elements of the list, not the indices. So you can use the item directly:
listcont.append(i + str(cont))
Second, you shouldn't name things list since it shadows the built-in of that name and will cause all kinds of trouble.
Third, the while loop would be better written as a for with a range
n = 5
my_list = ['q', 'p']
listcont = []
for counter in range(1, n+1):
for item in my_list:
listcont.append(item + str(counter))
Finally, you can simplify all of this into a list comprehension and make it look neater with an f-string:
def make_list(my_list, limit):
return [f'{item}{counter}' for counter in range(1, limit+1) for item in my_list]
make_list(['p', 'q'], 5)
When you use for loop, you must know that if you are using for i in list it means that i here is the element of the list, and the loop will traverse each element of the list.
While, what you want to do is for i in range(len(list)), this will traverse the list with i as a number which can gain a value, less than or equal to len(list) - 1.
You can learn this very basic thing about for loop here and hold yourself back from asking such questions.
Hope it helps, thanks.
You have a variable called list which is a bad idea because list is the type of a list in Python. But this isn't the issue. I'm guessing the function you have there, which is missing the declaration, is the function def concat(list, n), and you intended to write while cont <= n.
If all this is the case, when you do
for i in list:
i is going to be members of the list, so 'q', then 'p'. In this case list['p'] doesn't make any sense.
To get the output you're going for I would do (to be easy to read):
def concat(lst, n):
result = []
for i in range(n):
for v in lst:
result.append('{}{}'.format(v, i+1))
return result
You could do the whole thing in one line with:
['{}{}'.format(value, count + 1) for count in range(n) for value in lst]

Iteration to Recurssion

so myListToPyList(lst): takes lst, a MyList object and returns a Python list containing the same data
def myListToPyList(lst):
return myListToPyListRec(lst.head)
here's my helper function:
def myListToPyListRec(node):
if node == None:
return
else:
st1 = []
st1.append(node.data)
myListToPyListRec(node.next)
return st1
it's not working correctly.
Now here is my iterative solution that works correctly:
def myListToPyList(lst):
"""
Takes a list and returns a python list containing
the same data
param; lst
return; list
"""
st1 = []
curr = lst.head
while curr != None:
st1.append(curr.data)
curr = curr.next
return st1
Your current recursive code doesn't work because each time it gets called, it creates a new empty list, adds a single value to the list, then recurses (without passing the list along). This means that when the last item in the link list is being processed, the call stack will have N one-element Python lists (where N is the number of list nodes).
Instead, you should create the list just once, in your non-recursive wrapper function. Then pass it along through all of the recursion:
def myListToPyList(lst):
result_list = [] # create just one Python list object
myListToPyListRec(lst.head, result_list) # pass it to the recursive function
return result_list # return it after it has been filled
def myListToPyListRec(node, lst):
if node is not None # the base case is to do nothing (tested in reverse)
lst.append(node.data) # if node exists, append its data to lst
myListToPyListRec(node.next, lst) # then recurse on the next node
Because Python lists are mutable, we don't need to return anything in our recursive calls (None will be returned by default, but we ignore that). The list referred to by result_list in myListToPyList is the same object referred to by lst in each of the recursive calls to myListToPyListRec. As long as the recursive function mutates the object in place (e.g. with append) rather than rebinding it, they'll all see the same thing.
Note that recursion is going to be less eficient in Python than iteration, since function calls have more overhead than just updating a couple variables.
A while loop is equivalent to tail recursion, and vice versa. (One reason Python does not have automatic tail-call elimination is that the 'vice versa' part is rather easy.) The tail recursion requires that you add an accumulator parameter to be returned in the base case. Although I do not have a linked list for testing, I believe the following should work. (If not, this is close.) Python's default arguments make the helper either easier or unnecessary.
def myListToPyListRec(node, py_list=[]):
if node
py_list.append(node.data)
return myListToPyListRec(node.next, py_list)
else:
return py_list

Resources