Clone an undirected graph return deep copy - python

Given a reference of a node in a connected undirected graph, return a deep copy (clone) of the graph. Each node in the graph contains a val (int) and a list (List[Node]) of its neighbors. https://leetcode.com/problems/clone-graph/
Trying to solve this problem off of leetcode and not sure exactly where I'm stuck and how to get to the correct solution
Example:
class Solution(object):
def cloneGraph(self, node):
newNode = Node(node.val, [])
oldNode = node
dic = {}
dic[oldNode] = newNode
bfs = [oldNode]
while bfs:
oldNode = bfs.pop(0)
newNode = dic[oldNode]
neighbors = []
for item in oldNode.neighbors:
if item not in dic:
copy = Node(item.val, [])
dic[item] = copy
neighbors.append(copy)
bfs.append(item)
else:
copy = dic[item]
neighbors.append(copy)
newNode.neighbors = neighbors
return newNode

Change your last line to
return dic[node]
and it will work. You need to return the cloned node of the node sent to the function in the first place, and while newNode is that before going through your while bfs: loop, it gets changed during the loop.

Related

Removing a node from binary tree

I'm currently working on leetcode problem 366 where we have to find list of lists that contains values of leaves of each generation. I wanted to achieve this by recursion where if a node does not have left or right child, the value is recorded then the node removed by setting it to None. Here is my code:
def findLeaves(self, root: Optional[TreeNode]) -> List[List[int]]:
leaf_list = []
sub_list = []
def traverse(node):
if node == None:
return
if node.left == None and node.right == None:
sub_list.append(node.val)
node = None
return
traverse(node.left)
traverse(node.right)
return root
while True:
if root == None:
break
sub_list = []
traverse(root)
leaf_list.append(sub_list)
print(leaf_list)
return leaf_list
The problem seems to be that when a certain node is set to None, that change isn't retained. Why is it that I can't set a node to None to remove it?
Thanks
The tree can only be mutated when you assign to one if its node's attributes. An assignment to a variable, only changes what the variable represents. Such assignment never impacts whatever previous value that variable had. Assigning to a variable is like switching from one value to another without affecting any data structure. So you need to adapt your code such that the assignment of None is done to a left or right attribute.
The exception is for the root node itself. When the root is a leaf, then there is no parent to mutate. You will then just discard the tree and switch to an empty one (None).
One way to achieve this, is to use the return value of traverse to update the child-reference (left or right) that the caller of traverse needs to update.
Here is your code with those adaptations:
def findLeaves(root):
sub_list = []
def traverse(node):
if not node:
return
if not node.left and not node.right:
sub_list.append(node.val)
return # By returning None, the parent can remove it
node.left = traverse(node.left) # Assign the returned node reference
node.right = traverse(node.right)
return node # Return the node (parent does not need to remove it)
leaf_list = []
while root:
sub_list = []
root = traverse(root)
leaf_list.append(sub_list)
return leaf_list

Leetcode 133. Clone Graph with dfs understanding

Hi I have a problem understanding dfs. What I know is DFS has two versions; we mark visited before call and after call.
def solution1(start):
def dfs1(cur):
for nei in cur.neighbors:
if nei not in visited:
## mark visit before call
visited.add(nei)
dfs1(nei)
## drive dfs1
visited = set()
visited.add(start)
dfs1(start)
def solution2(start):
def dfs2(cur):
## mark visit after call
visited.add(cur)
for nei in cur.neighbors:
if nei not in visited:
dfs2(nei)
## drive dfs2
dfs2(start)
However, when I applied version1 (mark visited before a call) to the problem(https://leetcode.com/problems/clone-graph/), it complained and did not copy.
This is my solution:
"""
# Definition for a Node.
class Node:
def __init__(self, val = 0, neighbors = None):
self.val = val
self.neighbors = neighbors if neighbors is not None else []
"""
class Solution:
"""
def dfs1(cur):
for nei in cur.neighbors:
if nei in visited: continue
visited.add(nei)
dfs1(nei)
visited.add(start_node)
dfs1(start_node)
"""
def dfs(self, cur, visited):
new_node = Node(cur.val)
# visited[cur.val] = new_node
new_neighbors = []
for nei in cur.neighbors:
if nei.val not in visited:
visited[nei.val] = nei
new_neighbors.append(self.dfs(nei, visited))
else:
new_neighbors.append(visited[nei.val])
new_node.neighbors = new_neighbors
return new_node
def cloneGraph(self, node: 'Node') -> 'Node':
if node == None:
return None
visited = {}
visited[node.val] = node
return self.dfs(node, visited)
Let me know why this has problem. I don't understand why it does not work.
Main reason that your code doesn't work is because you store original node inside visited dictionary. You have to store processed DFS clone of a node inside visited. Why? Because inside new_neighbors.append(visited[nei.val]) you add visited node to neighbours of new cloned node. But any cloned node should have only cloned neighbours, not originals.
I decided to implement my own version of your algorithm/idea using DFS, next code is successfully accepted by LeetCode system (all tests pass):
class Solution:
def __init__(self):
self.visited = {}
def cloneGraph(self, node: 'Node') -> 'Node':
if node is None:
return None
if node.val in self.visited:
return self.visited[node.val]
nnode = Node(val = node.val)
self.visited[nnode.val] = nnode
nnode.neighbors = [self.cloneGraph(c) for c in node.neighbors]
return nnode
We use the hash map to create a map from original nodes to copied nodes. Then we use the hash map to check if we visit the node before or not.
def cloneGraph(self,node):
# I used closure instead of passing this object
visited={}
def dfs(node):
if node in visited:
# this returned value will be used inside for loop copy.neighbors.append(dfs(nei))
return visited[node]
#if node is not in visited we add it
# this is the first part of the copy. first copy the val
copy=Node(node.val)
visited[node]=copy
# we create the neighbors array of "copy" node
for nei in node.neighbors:
copy.neighbors.append(dfs(nei))
return copy
return dfs(node) if node else None

Why creating an instance of a class within a class method changes the 'self' argument?

I'm writing a linked list in Python and I've come across an issue which is really troublesome and terrible for debugging and I feel like I'm missing something about Python. I'm supposed to create a singly linked list with some basic functionalities. One of them is the take() function, which is meant to create a new list of n first elements of the original list.
However, creating a new instance of the LinkedList class seems to change the .self parameter and the variable node is modified, as the attribute .next is turned to None. In result, when creating a list and then trying to make a new one out of the n elements of it, the program runs indefinitely, but no matter which part I look at, I cannot find the loop or the reason behind it.
class LinkedList:
def __init__(self, head=None):
self.head = head
def is_empty(self):
if self.head == None:
return True
else:
return False
def add_last(self, node):
if self.is_empty():
self.head = node
return
nextEl = self.head
while True:
if nextEl.next is None:
nextEl.next = node
return
nextEl = nextEl.next
def take(self, n):
node = self.head
newHead = self.head
newHead.next = None
newList = LinkedList(newHead)
count = 0
while count < n:
newList.add_last(node.next)
node = node.next
count += 1
return newList
class Node:
def __init__(self, data, next=None):
self.data = data
self.next = next
Thank you for all your help.
In the take() function the line
newHead.next = None
modifies a node of the linked list, breaking this list. You can fix this as follows:
def take(self, n):
node = self.head
newHead = Node(self.head.data)
newList = LinkedList(newHead)
count = 0
while count < n:
newList.add_last(node.next)
node = node.next
count += 1
return newList
This, however, still will not work correctly since there is a problem with the add_last() function too. This function, I think, is supposed to add a node as the last element of a linked list, but since you do not modify the next attribute of the node, you actually append a whole linked list starting with that node. This can be fixed in the following way:
def add_last(self, node):
if self.is_empty():
self.head = node
return
nextEl = self.head
while True:
if nextEl.next is None:
nextEl.next = Node(node.data)
return
nextEl = nextEl.next
There are more issues. For example, take(sefl, n) will actually create a list of n+1 elements and will throw an exception if it is applied to a linked list that does not have that many elements.

How does Python represent the LinkedList in terms of memory reference?

Note: I prefer to not use any external module since it is for interview prep purpose.
I know that there is no a built-in linked-list DS in python. However, we can implement the linked-list through a class Node. In the following code, I did a method (intersect_ll) to find an intersection between two linkedlists where the definition of intersections: node(s) are in the same order and value in the both linkedlists:
class Node:
def __init__(self, data=None, next=None):
self.data = data
self.next = next
class SingleLinkedList:
def __init__(self):
self.head = None
def add_node(self, data):
newNode = Node(data)
if self.head:
current = self.head
while current.next:
current = current.next
current.next = newNode
else:
self.head = newNode
def print_ll(self):
current = self.head
ll_data = []
while current:
ll_data += [current.data]
current = current.next
return ll_data
def intesect_ll (self, first_ll, second_ll):
current_first = first_ll.head
current_second = second_ll.head
if current_first is None or current_second is None:
return False
list_intersect = []
while current_first and current_second:
if current_first.data == current_second.data:
list_intersect += [current_first.data]
current_first = current_first.next
current_second = current_second.next
for item in list_intersect:
self.add_node(item)
return self.print_ll()
My Question is: :
I am pretty new to python so I am struggling to understand why comparing instead by memory reference is not working. In other word, why python did not give these two nodes with the same value and order in both linkedlists, the same memory location ?!. Is that because I am implementing my own data-structure and hence,I assume that python would take care of the rest which is not the reality?
if current_first is current_second
compiler result:
it gives two different memory references for the both nodes of the same value and order in both linkedlists. Hence, this does not work and need to make it (.data) comparing by value then.
I found out that what I expect from python to do in terms of memory management is not that what really happens. I need to allocate the same object and then reference it in both linked lists in order to comparing by reference becomes a valid solution.
assign ObjectA : locationA in memory
linkedlist1: node.next --> LocationA ---> n nodes
linkedlist2: node.next --> LocationA ----> k nodes
Otherwise, they are completely two different linkedlists and the interpreter would assign each one of the nodes into different memory location:
linkedlist1: node.next ---> locationX -- n nodes
linkedlist2: node.next ---> locationY --- k nodes
Hope that helps anyone would have the same concern

How can I reference a list initialized in a function? (Python)

The error I'm receiving is local variable 'actions' referenced before assignment. I have a feeling that this is because I am creating the list within the function and then trying to reference it within. This is the specific code from which I receiving the error.
if node.state == target:
actions: []
cells: []
while node.parent is not None:
actions.append(node.action)
cells.append(node.state)
node = node.parent
This is more context for the origin of the code I am writing.
def shortest_path(source, target):
"""
Returns the shortest list of (movie_id, person_id) pairs
that connect the source to the target.
If no possible path, returns None.
"""
# Create a frontier
frontier = QueueFrontier()
# Initialise explored set
explored = set()
# Create a start Node
node = Node(state=source, parent=None, action=None)
# Start with the source as a node (The initial state) and add it to the frontier
frontier.add(node)
# Start looping until we get a solution
while True:
# Check if Frontier is empty then no solution
if frontier.empty():
raise Exception("No path found")
# Remove node from the frontier
node = frontier.remove()
# Check if node contains the goal state
if node.state == target:
actions: []
cells: []
while node.parent is not None:
actions.append(node.action)
cells.append(node.state)
node = node.parent
actions.reverse()
cells.reverse()
solution = (actions, cells)
return solution
I really appreciate any help on this.
use = to assign
if node.state == target:
actions = []
cells = []
while node.parent is not None:
actions.append(node.action)
cells.append(node.state)
node = node.parent

Categories

Resources