Palindrome Linked List in Python with O(1) Extra Space - python

This is the #234 Leetcode problem:
Given a singly linked list, determine if it is a palindrome.
Follow up: Could you do it in O(n) time and O(1) space?
This problem is easy to solve with O(n) space. However, I cannot figure out the O(1) solution. The only way I think of is to use recursion:
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
current = None
def isPalindrome(self, head):
"""
:type head: ListNode
:rtype: bool
"""
if not head or not head.next:
return True
self.current = head
return self.compare(head)
def compare(self, head):
if not head: return True
if not self.compare(head.next): return False
if head.val != self.current.val:
return False
else:
self.current = self.current.next
return True
This works with small samples but gives
maximum recursion depth exceeded
Anyone can provide a solution using only O(1) space? Thank you.

If you are allowed to modify the list in place, you can do the following:
Iterate over the list to count how many elements there are.
Iterate a second time, and starting from the middle of the list, reverse the pointers in the list nodes so that they point to the previous node rather than the next. Remember the last node.
Now you have a pointer to the first node and the last node, and can iterate towards the middle from either end, until you either find a difference (no palindrome) or reach the middle (palindrome).
Reverse the pointers in the second half to bring them back to their original state.
This will only require constant additional space, and has linear execution time.

I commented key points on the solution:
class Solution:
def with_array(self,head:ListNode)->bool:
# this makes S:O(N)
nums=[]
while head:
nums.append(head.val)
head=head.next
l,r=0,len(nums)-1
while l<=r:
if nums[l]!=nums[r]:
return False
l+=1
r-=1
return True
def optimum(self,head:ListNode)->bool:
fast_pointer=head
slow_pointer=head
# I wanna reach the end of the linked list. I stop when fast_pointer.next=None
while fast_pointer and fast_pointer.next:
# we are forwarding fast_pointer twice
fast_pointer=fast_pointer.next.next
# while slow reach middle, fast will reach to the end
slow_pointer=slow_pointer.next
# at the end of this while loop, slow_pointer will be in middle, fast_pointer will be at the end
# reverse the second half of the list, from slow_pointer till the fast_pointer
# at the end of the reverse, prev will point to the last node
prev=None
while slow_pointer:
temp=slow_pointer.next
slow_pointer.next=prev
# eventually prev will point to the last node
prev=slow_pointer
slow_pointer=temp
# check if it is a palindrome
# remember, after reversing, prev=tail
left,right=head,prev
while right:
if left.val!=right.val:
return False
left=left.next
right=right.next
return True

Related

How to return value from the bottom of recursive stack

I try to figure out how to return value from the bottom of the recursive stack.
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
kth = -1
def helper(curr, prev, length):
if not curr:
return length - n
length += 1
kth = helper(curr.next, curr, length)
if length == kth:
prev.next = curr.next
dummy = head
helper(head, None, 1)
return dummy
The first stack unwind set the kth value, next will be None. I can't figure out how to pass it forward to the top.
I understand why it works in that way, only seek for resolution. Not explanation.
There are more memory efficient solutions, but if you want to do it this way, then:
There is no return statement that is executed except in the base case, this means that a caller will get None from the recursive call, except for one time.
There is no need to pass more than the second argument to helper, i.e. prev. curr can be derived from it, and the length parameter is not needed: you can return n in the base case, and decrease that value as you backtrack.
The idea of a dummy is nice, but then the idea is that it is a new node that precedes head in the extended linked list. Making only a synonym for head brings you nothing useful.
Here is your code with those three issues fixed:
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
def helper(prev):
if not prev.next:
return n
i = helper(prev.next) - 1
if i == 0:
prev.next = prev.next.next
return i
dummy = ListNode(0, head)
helper(dummy)
return dummy.next

what will I need to return for list and do I need code for edge case duplicate?

I have the following ListNode class:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
I wrote the following code with the aim of removing any duplicates from a linked list:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if head == None:
return None
if head.next == None:
return head
curr = head
nex = head.next
while nex:
if head.val == nex.val:
curr.next = nex.next
curr = nex.next
nex = curr.next
Questions:
What do I return at the end? Is it 'head' or 'curr' or something else? And why?- I know that I want the output to be a linked list of the input but with duplicates removed; so does it make sense to return the head of the linked list? But when I do this, I don't quite get the expected results, namely [1,1,2,2,3,3] -> [1,2,3,3]; which leads me to my next question...
Do I need to write an edge case for when there is a duplicate right at the end of the linked list? As I don't believe my code below deals with this -- am I right?
I'll assume that your list is always sorted, so that any duplicates are always sitting next to each other in the list.
There are two issues in your while loop:
You should compare the values from the two adjacent nodes, but your code is always comparing the next value with the head node's value. So change:
if head.val == nex.val:
to
if curr.val == nex.val:
Your code is skipping nodes by doing curr = nex.next. This means that curr will never be nex. This also has as effect that the next statement may be an invalid reference, as curr might be None. So change:
curr = nex.next
to
curr = nex
When you detect a duplicate, only nex should move to the next node. curr should stay where it is, as it may have more than one duplicate to deal with. So the above assignment to curr should only happen when you don't have a duplicate.
The corrected while loop is as follows:
while nex:
if curr.val == nex.val:
curr.next = nex.next
else:
curr = nex
nex = curr.next
What to return
Since your function seems to be designed to return a ListNode, just make sure to always return head. So add that statement also after the while loop.
In my opinion it shouldn't have been necessary for this function to return the head node, since the value of head will never change by this function. So the caller already knows the value.
NB: you don't need to deal specifically with this case:
if head.next == None:
return head
This case will just mean that the while loop will not have a single iteration. As said above, just add a return statement at the very end of the function, and then you can drop the above if block:
return head

Sort a linked list, handling pairs as a single item

I am working on this challenge:
Sort a Linked list in increasing order by considering two nodes as a two digit number. (in place sorting)
4 ≤ n ≤ 1000
The length of the linked list is always even.
Example 1:
Input
1→3→4→2→1→2
Output
1→2→1→3→4→2
Explanation:
12 > 13 > 42
Example 2:
Input
1→3→0→3
Output
0→3→1→3
Here is my linked list template implementation; which anyone can start coding:
class Node():
def __init__(self,val):
self.data = val
self.next = None
class Linkedlist():
def __init__(self):
self.head = None
def add(self,x):
t = Node(x)
t.next = self.head
self.head = t
def display(self):
p = self.head
while p != None:
print(p.data)
p=p.next
def sort(self):
curr = self.head
# your logic here
m = Linkedlist()
m.add(1)
m.add(3)
m.add(4)
m.add(2)
m.add(1)
m.add(2)
m.display()
Which is the algorithm to sort the pairs in a linked list (in place), and how can I code it?
First write a function that accepts a linked list of single digit nodes and merges each pair of adjacent single digit nodes into a linked list of half as many nodes, where each node contains a double digit number.
Then sort the resulting linked list using bubble sort, which is O(n^2), or mergesort, which is O(nlogn).
Then, if need be, write a third function that takes apart the double digit nodes and creates a new list of twice as many single digit nodes.
Doing it with two or three functions like this will really simplify the sort.
First of all, your current code will not generate list 1→3→4→2→1→2, but its inverse. This is because your definition of the add method will prepend the given value to the list.
To make add work correctly, it should first find the last node in the list, and append the new node after it. As this is not a very efficient method to add many values to a list, I would suggest to also define add as a Node method, and to allow chaining add calls. That way you can add many values in one chained expression without being inefficient.
Also, as you will need to compare values of pairs, it makes sense to make a method on Node that will return the value of the pair formed by that node and the next in the list.
Finally, for the sort algorithm itself, you could reduce the list to its first pair only (which is then obviously sorted), and treat the rest as a separate list of unsorted nodes. Then extract a pair at a time from the unsorted list, and find the right place to inject it in the main list, so that it remains sorted.
This process represents O(n²) time complexity on average and in the worst case. The best case is O(n), which occurs when the list is originally sorted in reverse order.
Here is how that could look:
class Node():
def __init__(self, val):
self.data = val
self.next = None
# Easy chaining: defining this method on a Node as well
def add(self, val):
node = Node(val)
node.next = self.next
self.next = node
return node
# Define how the value of a pair is calculated
def pairvalue(self):
return self.data * 10 + self.next.data
class Linkedlist():
def __init__(self):
self.head = None
def add(self, val):
# Corrected method: addition should be at the end of the list
node = Node(val)
if not self.head:
self.head = node
else: # Look for the last node in the list
curr = self.head
while curr.next:
curr = curr.next
# ...and append the new node after it
curr.next = node
return node # Allow for chaining
def display(self):
p = self.head
while p:
print(p.data, end= "→")
p = p.next
print("NIL")
def sort(self):
if not self.head:
return
# Reduce list to just its first pair
# The remainder is a temporary "todo" list
todo = self.head.next.next
self.head.next.next = None
while todo:
pair = todo # The pair that will be added to the sorted list
todo = todo.next.next
val = pair.pairvalue()
if val < self.head.pairvalue():
# The pair is a minimum: prepend it
pair.next.next = self.head
self.head = pair
else:
curr = self.head.next # odd index
# Find insertion point in sorted list
while curr.next and curr.next.pairvalue() < val:
curr = curr.next.next
# Perform insertion
pair.next.next = curr.next
curr.next = pair
m = Linkedlist()
m.add(1).add(3).add(4).add(2).add(1).add(2)
m.display() # original list
m.sort()
m.display() # final list
The output:
1→3→4→2→1→2→NIL
1→2→1→3→4→2→NIL

PYTHON Binary Search Tree - Recursive Remove

I'm working on a binary search tree class and we have to implement the remove method using recursion. Here is the code that was given to us.
def remove (self, x):
def recurse (p):
# Modify this method.
if p==None:
return p
elif x<p.data:
return p
elif x>p.data:
return p
elif p.left==None and p.right==None: # case (1)
return p
elif p.right==None: # case (2)
return p
elif p.left==None: # case (3)
return p
else: # case (4)
return p
self.root = recurse (self.root)
Obviously there should be more than just return p for each conditional. And I'm pretty sure the first if and two elifs are used to 'locate' the node containing x. However I am not sure how to implement the four cases. Any input would be appreciated.
Also, the end goal is to use this method to iterate through the BST and remove each node.
Well, your first step is to locate X, remember that you do this by recursion, so you should recurse the remove function on the child tree its located. (left if x < p.data, right if higher... only if they exist). Next, you need to worry what to do once you find x (x = p.data).
I would recommend drawing a tree and think as an algorithm. :)
Remember the BST property! The tree will necesarily modify it's structure to preserve the property, so.. who is the new father of the lonely childs?
hints:
a sibling possibly?
.
remember the BST charasteristic
.
recursion!!!)
pseudo_code method_recurse requires argument: this_node
if this_node is None then return None Found
if this_node is target then return this_node
if this_node is less_than target then return call self with this_node.right
if this_node is greater_than target then return call self with this_node.left

Finding a node in a tree

I am having trouble finding a node in a tree with arbitrary branching factor. Each Node carries data and has zero or greater children. The search method is inside the Node class and
checks to see if that Node carries data and then checks all of that Nodes children. I keep ending up with infinite loops in my recursive method, any help?
def find(self, x):
_level = [self]
_nextlevel = []
if _level == []:
return None
else:
for node in _level:
if node.data is x:
return node
_nextlevel += node.children
_level = _nextlevel
return self.find(x) + _level
The find method is in the Node class and checks if data x is in the node the method is called from, then checks all of that nodes children. I keep getting an infinite loop, really stuck at this point any insight would be appreciated.
There are a few issues with this code. First, note that on line 2 you have _level = [self]. that means the if _level == [] on line 5 will always be false.
The 2nd issue is that your for loop goes over everything in _level, but, as noted above, that will always be [self] due to line 2.
The 3rd issue is the return statement. You have return self.find(x) + _level. That gets evaluated in 2 parts. First, call self.find(x), then concatenate what that returns with the contents of _level. But, when you call self.find(x) that will call the same method with the same arguments and that, in turn, will then hit the same return self.find(x) + _level line, which will call the same method again, and on and on forever.
A simple pattern for recursive searches is to use a generator. That makes it easy to pass up the answers to calling code without managing the state of the recursion yourself.
class Example(object):
def __init__(self, datum, *children):
self.Children = list(children) # < assumed to be of the same or duck-similar class
self.Datum = datum
def GetChildren(self):
for item in self.Children:
for subitem in item.GetChildren():
yield subitem
yield item
def FindInChildren(self, query): # where query is an expression that is true for desired data
for item in self.GetChildren():
if query(item):
yield item

Categories