I am working on LeetCode problem 430. Flatten a Multilevel Doubly Linked List:
You are given a doubly linked list, which contains nodes that have a
next pointer, a previous pointer, and an additional child pointer.
This child pointer may or may not point to a separate doubly linked
list, also containing these special nodes. These child lists may have
one or more children of their own, and so on, to produce a multilevel
data structure as shown in the example below.
Given the head of the first level of the list, flatten the list so
that all the nodes appear in a single-level, doubly linked list. Let
curr be a node with a child list. The nodes in the child list should
appear after curr and before curr.next in the flattened list.
Return the head of the flattened list. The nodes in the list must have
all of their child pointers set to null.
My Approach
This is what I intended to do:
Traverse through the head node and wherever a child node appears, change the link of the current node to the child node.
Then, add all the nodes traversed to a separate LList (in my code it is ll). I have kept dummy at the head of the new LList.
While reaching at every child node, add that node address to a stack. So, when doing a pop() operation, the last child node added will be retrieved first.
After that, pop and iterate through that child node till None and add new nodes to new LList i.e. ll. Lastly, return dummy.next
My Code
class Solution:
def flatten(self, head: 'Optional[Node]') -> 'Optional[Node]':
curr = head
ll = dummy = ListNode(0)
stack = []
while curr:
if curr.child:
stack.append(curr)
ll.next = curr
curr.next = curr.child
else:
ll.next = curr
ll = ll.next
curr = curr.next
while stack:
curr_node = stack.pop()
while curr_node:
ll.next = curr_node
ll = ll.next
curr_node = curr_node.next
return dummy.next
The Problem
I am getting a Time Limit Exceeded warning. Can someone please tell me if I am correct with my approach and where I am going wrong?
The second part of your code is creating a cycle in the ll list. The node that you pop from the stack was already put in the ll list at the time you pushed that node on the stack. But you append it again to the ll list, making a cycle. And so while curr_node: is becoming an infinite loop.
The general idea for the algorithm is fine though, but push the next node on the stack, instead of the current node.
Furthermore, you should:
Clear the child reference once you have processed it
Adapt the prev references, so they are consistent with the changes to some of the next references. Remember: it is a doubly linked list you get and need to return.
I would not:
Lag behind with a separate ll reference. You can just work with curr
Use the dummy node. Although I understand its utility, once you have dealt with the case of the empty list (as a boundary case), there is really no benefit anymore
So here is how I would do it:
class Solution:
def flatten(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return
stack = []
current = head
while current:
if current.child:
if current.next:
stack.append(current.next)
current.next = current.child
current.next.prev = current
current.child = None
elif not current.next and stack:
current.next = stack.pop()
current.next.prev = current
current = current.next
return head
Related
I am attempting to use DFS to solve a graph problem, and have ran into a wall. This is my implementation in Python3. unvisited is an array of ints, start and end are ints in unvisited, path is an empty array filled out as the DFS goes, and edges is a dictionary of edges.
def traverse(unvisited, start, end, path, edges):
copy_unvisited = unvisited.copy()
copy_path = path.copy()
current = start
copy_unvisited.remove(current)
copy_path.append(current)
if current == end and len(copy_unvisited)==0:
#print is just for me to check my answers
print(copy_path)
return copy_path
for i in edges[current]:
if i in copy_unvisited:
return traverse(copy_unvisited, i, end, copy_path, edges)
The goal is to find a path that starts on start and visits every int in unvisited until ending on end. Therefore, I'm running into a problem with the recursion (I think) because in cases where the path is wrong, I don't want to return anything; instead, I want the DFS to continue. With the way the code is written now, I'm getting "None" returned, even though the correct solution is being printed (because of my print statement). How can I fix this issue?
Thanks in advance!
Edit: If unvisited = [1,3,4,5], start = 4, end = 5, edges = {1: (4,5), 3: (1), 4: (1,3,5), 5: (1,4)},
traverse(unvisited, 4, 5, [], edges) should return [4,3,1,5], but instead I get None. traverse does not have a return type if a wrong path is followed, which is why I think I'm getting None. It eventually finds the right path and prints it.
I have found a solution by introducing a new parameter sol, and when the correct path is reached, I copy each entry of copy_path into sol. Then, I return sol at the END, after the recursive call. I also removed the return statement for the recursive call.
def traverse(unvisited, start, end, path, edges,sol):
copy_unvisited = unvisited.copy()
copy_path = path.copy()
current = start
copy_unvisited.remove(current)
copy_path.append(current)
if current == end and len(copy_unvisited)==0:
for i in copy_path:
sol.append(i)
for i in edges[current]:
if i in copy_unvisited:
traverse(copy_unvisited, i, end, copy_path, edges,sol)
return sol
Feels a little inelegant, so I'm open to better ways to solve this problem!
First of all, with the input that you gave, the output should be [4 1 5], since the first edge linked to the vertex 4 is 1 and at the time of the first execution of the DPS 1 still in the unvisited list.
The main problem with your original code is these two instructions:
The
len(copy_unvisited)==0:
in the first if and the
if i in copy_unvisited:
at the end.
Basically you have this:
First call of the function:
node 4, unvisited (uv): [1,3,4,5], path=[]
At the for: 4 uv: [1,3,5] edges: (1, 3, 5)
So i=1, i is in uv, then:
Second call of the function:
node 1, uv: [1,3,5], path=[4]
At the for: 1 uv: [3,5] edges: (4,5)
So i=4, but 4 is not in uv,
So i=5, 5 is in uv, then:
Third call of the function *OBS1*
node 5, uv: [3,5], path=[4,1]
it will not enter in the first if, because len(uv) is not equals 0.
At the for: 5 uv: [3] edges: (1,4)
So i=1, but 1 is not in uv
So i=4, but 4 is not in uv
The for ends, so the function ends and not call itself, neither return a value.
I am pretty sure the DPS should end when you get in the wanted node, or, when you have no more nodes to visit. In the first case, it should return the path it found, if not, it means the end node there is no path from the start node to the end node (you have it in a disconnected graph).
I tried modify your implementation, but I found it difficult to deal with this "unvisited" variable and with the path being updated in this way. I don't think you are conceptually incorrect, both in the overview of how DFS should work, and in how to update the recurrence, but I was unable to make it work that way and so I modified the algorithm so that it updated the path to as it unstack the functions, instead of doing so while it calls the functions.
def traverse(start,end,edges,visited=[],path = []):
if start == end:
path.append(start)
return path
if start not in visited:
visited.append(start)
if start not in edges:
return path
for i in edges[start]:
path = traverse(i,end,edges,visited,path )
if len(path)>0:
break
if len(path)>0 and start not in path:
path.append(start)
return path
This will return the path, written in reverse, if you want in the normal order just take the final result of the function, which will be a list and apply the .reverse () method on it.
This implementation above is a slight modification of the one I found here: https://likegeeks.com/depth-first-search-in-python/ I just added the part of finding a path between two specific points.
Your "sol" solution work, partially, because, when the DFS get into the the third call and left the for without calling the function again, it, at least can return a empty list, then the third call leaves the stack and the algorithm continues to the next i of the second function call, that is 3. The problem with this implementation is the DFS will read all nodes before return a path to the destination.
I have this code right down here:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if head == None:
return
pre, node = None, head
while node:
pre, node.next, node = node, pre, node.next
return pre
I am trying to vizualize how this works. If it starts on a list, the pre becomes the head, since node was assigned to head. then node.next is assigned to pre, so it points to itself? Finally, node becomes node.next, which is itself? am I missing something here?
Multiple assignment isn't the same as several assignments one after the other. The difference is that the values on the right hand side of the statement all get evaluated before anything get rebound. The values on the right hand side are in fact packed up in a tuple, then unpacked into the names on the left hand side.
That's important in this situation as it means that node.next on the right hand side gets its value saved, so that when you rebind it to something else (pre), the old value is still available to become the new node value after the assignment.
You may want to play around with some simpler assignments, like the classic swap operation:
x = 1
y = 2
x, y = y, x # swap x and y's values
print(x, y) # prints "2 1"
_tup = y, x # this is how it works, first pack the RHS values into a tuple
x, y = _tup # then unpack the values into the names on the LHS
print(x, y) # prints "1 2" as we've swapped back
The main idea is to convert the original head node becomes the last node of the new linked list and convert the original last one become the new head node and convert the link direction between nodes.
suppose the original linked list consists 2 nodes.
first, pre = None, the node = head, then node.next = pre that means the original head node becomes the last node of the new linked list. node = node.next that means to convert the link direction between nodes. node.next = pre means to convert the original last one becomes the new head.
while repeatedly execute the above process
Here is a related question that links to docs on evaluation order: Tuple unpacking order changes values assigned
From https://docs.python.org/3/reference/expressions.html#evaluation-order, the example expr3, expr4 = expr1, expr2 shows evaluation order through the suffix number. It shows that the right side of assignment is evaluated first, from left to right, then the left side of assignment is evaluated, also from left to right.
For mutable objects like in this question, it gets more confusing without knowing the evaluation order.
To prove that it is indeed left-to-right on the left-hand-side, you can imagine what happens when pre, node.next, node = node, pre, node.next is assigned from right-to-left, meaning:
node = node.next
node.next = pre
pre = node
This wouldn't be reversing the Linked List at all.
Other ways to write this reversal:
Sometimes you can see others express this pattern as
pre, pre.next, node = node, pre, node.next
(Notice the 2nd element on LHS changed from node.next to pre.next.
This still works because after the first evaluation of pre = node, pre and node are referring to the same node. However, it introduces an extra dependency on the first evaluation of pre = node, which adds unnecessary cognitive load on the reader.
If we remained at pre, node.next, node = node, pre, node.next, then even swapping the first two variables (do it on both left and right of assignment) works:
node.next, pre, node = pre, node, node.next.
This is also my most preferred form since the right-hand-side naturally follows a previous,current,next order of the linked list.
Generally, we should place the dependent objects on the left of independent objects when ordering a tuple of variables on the left-hand-side. Any ordering with node = node.next before node.next = pre should break the implementation. (One example already shown in the thought experiment above on right-to-left evaluation order.)
I am implementing the merge sort algorithm in Python. Previously, I have implemented the same algorithm in C, it works fine there, but when I implement in Python, it outputs an unsorted array.
I've already rechecked the algorithm and code, but to my knowledge the code seems to be correct.
I think the issue is related to the scope of variables in Python, but I don't have any clue for how to solve it.
from random import shuffle
# Function to merge the arrays
def merge(a,beg,mid,end):
i = beg
j = mid+1
temp = []
while(i<=mid and j<=end):
if(a[i]<a[j]):
temp.append(a[i])
i += 1
else:
temp.append(a[j])
j += 1
if(i>mid):
while(j<=end):
temp.append(a[j])
j += 1
elif(j>end):
while(i<=mid):
temp.append(a[i])
i += 1
return temp
# Function to divide the arrays recursively
def merge_sort(a,beg,end):
if(beg<end):
mid = int((beg+end)/2)
merge_sort(a,beg,mid)
merge_sort(a,mid+1,end)
a = merge(a,beg,mid,end)
return a
a = [i for i in range(10)]
shuffle(a)
n = len(a)
a = merge_sort(a, 0, n-1)
print(a)
To make it work you need to change merge_sort declaration slightly:
def merge_sort(a,beg,end):
if(beg<end):
mid = int((beg+end)/2)
merge_sort(a,beg,mid)
merge_sort(a,mid+1,end)
a[beg:end+1] = merge(a,beg,mid,end) # < this line changed
return a
Why:
temp is constructed to be no longer than end-beg+1, but a is the initial full array, if you managed to replace all of it, it'd get borked quick. Therefore we take a "slice" of a and replace values in that slice.
Why not:
Your a luckily was not getting replaced, because of Python's inner workings, that is a bit tricky to explain but I'll try.
Every variable in Python is a reference. a is a reference to a list of variables a[i], which are in turn references to a constantant in memory.
When you pass a to a function it makes a new local variable a that points to the same list of variables. That means when you reassign it as a=*** it only changes where a points. You can only pass changes outside either via "slices" or via return statement
Why "slices" work:
Slices are tricky. As I said a points to an array of other variables (basically a[i]), that in turn are references to a constant data in memory, and when you reassign a slice it goes trough the slice element by element and changes where those individual variables are pointing, but as a inside and outside are still pointing to same old elements the changes go through.
Hope it makes sense.
You don't use the results of the recursive merges, so you essentially report the result of the merge of the two unsorted halves.
these examples come from Learning Python by Mark Lutz. The first function is a recursive function used to traverse a list with arbitrary nesting in order to calculate the sum of elements:
def sumtree_rec(L):
tot = 0
for x in L:
if not isinstance(x, list):
tot += x
else:
tot += sumtree(x)
return tot
The second function achieves the same thing, but without recursion:
def sumtree_notrec(L):
tot = 0
items = list(L)
while items:
front = items.pop(0)
if not isinstance(front, list):
tot += front
else:
items.extend(front)
return tot
I believe I understand how both of these functions work. I traced out how L changes in sumtree_notrec with each iteration over the code body, and it matches up with the output from the book. I also think I understand why the recursion is considered a stack, since every level pushes a call frame onto the runtime stack, and is popped off whenever the call is complete.
What I don't understand is why the recursive function is referred to as a FIFO queue? I looked it up and I feel like I understand what the data structures represent, I just don't see how they apply to this function. I also found this resource which explained a bit about the call stack: https://www.cs.ucsb.edu/~pconrad/cs8/topics.beta/theStack/02/
For example, if I trace through L in the non recursive function (not actual code, just a representation):
L --> [1,[2,[3,4],5],6,[7,8]]
L --> (1) is popped [[2,[3,4],5],6,[7,8]]
L --> [2,[3,4],5] is not popped
L --> [6,[7,8],2,[3,4],5]
etc...
Why is this called a queue? What object is 'first in' and then 'first out'?
The recursive version is a depth-first search. The non-recursive version is a breadth-first search. In the non-recursive version, the items list is treated as a queue. Whenever a list is popped from items, the individual elements in that list are added to the end of items.
That's the simple definition of a queue: elements are added to the back and removed from the front.
This question has somehow to do with an earlier post from me. See here overlap-of-nested-lists-creates-unwanted-gap
I think that I have found a solution but i can't figure out how to implement it.
First the relevant code since I think it is easier to explain my problem that way. I have prepared a fiddle to show the code:
PYFiddle here
Each iteration fills a nested list in ag depending on the axis. The next iteration is supposed to fill the next nested list in ag but depending on the length of the list filled before.
The generell idea to realise this is as follows:
First I would assign each nested list within the top for-loop to a variable like that:
x = ag[0]
y = ag[1]
z = ag[2]
In order to identify that first list I need to access data_j like that. I think the access would work that way.
data_j[i-1]['axis']
data_j[i-1]['axis'] returns either x,y or z as string
Now I need to get the length of the list which corresponds to the axis returned from data_j[i-1]['axis'].
The problem is how do I connect the "value" of data_j[i-1]['axis'] with its corresponding x = ag[0], y = ag[1] or z = ag[2]
Since eval() and globals() are bad practice I would need a push into the right direction. I couldn't find a solution
EDIT:
I think I figured out a way. Instead of taking the detour of using the actual axis name I will try to use the iterator i of the parent loop (See the fiddle) since it increases for each element from data_j it kinda creates an id which I think I can use to create a method to use it for the index of the nest to address the correct list.
I managed to solve it using the iterator i. See the fiddle from my original post in order to comprehend what I did with the following piece of code:
if i < 0:
cond = 0
else:
cond = i
pred_axis = data_j[cond]['axis']
if pred_axis == 'x':
g = 0
elif pred_axis == 'y':
g = 1
elif pred_axis == 'z':
g = 2
calc_size = len(ag[g])
n_offset = calc_size+offset
I haven't figured yet why cond must be i and not i-1 but it works. As soon as I figure out the logic behind it I will post it.
EDIT: It doesn't work for i it works for i-1. My indices for the relevant list start at 1. ag[0] is reserved for a constant which can be added if necessary for further calculations. So since the relevant indices are moved up by the value of 1 from the beginning already i don't need to decrease the iterator in each run.