I was attempting to solve the question "Find Ancestors of given node". For a fact I was able to solve the question. But I had an add-on question for which I am unable to alter the logic
So lets assume the Binary tree is like below :
100
/ \
45 150
/ \ / \
40 60 120 200
The common ancestor for Node 60 would be 100,45.
My Code :
def helper(node,k,path):
if node is None:
return path
path.append(node.data) # Append every node as we visit
if path[-1]==k: #Once we find the desired Node, print the list till last element
print(path[:-1])
path = helper(node.left,k,path)
path = helper(node.right,k,path)
del path[-1] #while backtracking , remove the leaf node
return path
def findCommonAns(root,k):
if root is None:
return None
if root.data == k:
return [root.data]
helper(root,60,[])
findCommonAns(tree.root,60)
My add-on question is, instead of printing, if I wanted to return the list (path), how can I modify this logic ? As I am learning recursion for just a week now, I am finding it hard to modify the above logic.
Any help is much appreciated. Thanks !
def helper(node,k,path):
#If node is empty, that is if either root is None,
#or if we have gone beyon leaf node, result the path list
if node is None:
return path
path.append(node.data) #Append data as we visit
if path[-1]==k:#If last item of the list is the node
return path[:-1] # return the elemenet till the last element
path = helper(node.left,k,path)
path = helper(node.right,k,path)
del path[-1]
return path
def findCommonAns(root,k):
if root is None:
return None
if root.data == k:
return [root.data]
path=[]
helper(root,k,path)
return path[:-1]
findCommonAns(tree.root,60)
Related
so I am writing a code to find the height of a binary search tree. My first thoughts were to traverse through both the right and left subtrees, append the values of the nodes in each subtree to a list, and then get the length of the list, and the longer list would be the height of the tree. Here is my code:
def getHeight(self,root):
lstr = []
lstl = []
if root.right is not None:
lstr.append(root.right.data)
if root.right.right is not None:
lstr.append(root.right.right.data)
if not root.left == None:
lstl.append(root.left.data)
if root.left.left is not None:
lstr.append(root.left.left.data)
return lstr
return lstl
However, is it possible to use a for loop to keep iterating through the .right.right.right and just continue using the .right attribute in the if statement until the for loop ends?
The example below is from a source online and what I'm not sure of is why we need to append to allPaths a new copy of currentPath. I thought that in deleting the last element as we go back up the recursive call stack by doing del currentPath[-1] makes sure that we are not adding the previous path to the new path
class TreeNode:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def find_paths(root, required_sum):
allPaths = []
find_paths_recursive(root, required_sum, [], allPaths)
return allPaths
def find_paths_recursive(currentNode, required_sum, currentPath, allPaths):
if currentNode is None:
return
# add the current node to the path
currentPath.append(currentNode.val)
# if the current node is a leaf and its value is equal to required_sum, save the current path
if currentNode.val == required_sum and currentNode.left is None and currentNode.right is None:
allPaths.append(list(currentPath))
else:
# traverse the left sub-tree
find_paths_recursive(currentNode.left, required_sum -
currentNode.val, currentPath, allPaths)
# traverse the right sub-tree
find_paths_recursive(currentNode.right, required_sum -
currentNode.val, currentPath, allPaths)
# remove the current node from the path to backtrack,
# we need to remove the current node while we are going up the recursive call stack.
del currentPath[-1]
def main():
root = TreeNode(12)
root.left = TreeNode(7)
root.right = TreeNode(1)
root.left.left = TreeNode(4)
root.right.left = TreeNode(10)
root.right.right = TreeNode(5)
required_sum = 23
print("Tree paths with required_sum " + str(required_sum) +
": " + str(find_paths(root, required_sum)))
main()
It is important to realise that during the whole process there is only one currentPath list. It is the one that is created in the initial call:
find_paths_recursive(root, required_sum, [], allPaths)
# ^^---here!
All that happens to that single list is that elements are appended to it, and then deleted from it again (when backtracking). It continually grows and shrinks, grows and shrinks,... throughout its lifetime. But it is the same, single list instance.
If you were to append that list to allPaths without taking a copy, i.e. like this:
allPaths.append(currentPath)
...then realise that while that list is a member of allPaths, it will be mutated by future append and delete actions on it! Even more: as the above statement is executed again later on:
allPaths.append(currentPath)
... exactly the same list is appended that is already in allPaths... because there is only one currentPath list! So you'll end up with allPaths having repeated references to one and the same list.
Concluding: it is important to take a copy of currentPath which will not be mutated any more by the future mutations on currentPath. It is like taking a snapshot of the current state of currentPath.
The find_paths_recursive function was designed such that appending to allPaths is the way to return the results to the caller.
def find_paths(root, required_sum):
allPaths = []
find_paths_recursive(root, required_sum, [], allPaths)
return allPaths
Here in find_paths, allPaths is passed to find_paths_recursive as an empty list and after it is done, it will contain the results (paths from root to leaf which fulfill the described condition).
I would recommend breaking the problem down into separate parts. First we write a generic paths function -
def paths (t = None, p = ()):
if not t:
return
elif t.left or t.right:
yield from paths(t.left, (*p, t.val))
yield from paths(t.right, (*p, t.val))
else:
yield (*p, t.val)
mytree = TreeNode \
( 12
, TreeNode(7, TreeNode(4))
, TreeNode(1, TreeNode(10))
)
Now we can see how paths works -
for p in paths(mytree):
print(p)
(12, 7, 4)
(12, 1, 10)
Now we can write solver that specializes paths -
def solver (t = None, q = 0):
for p in paths(t):
if sum(p) == q:
yield p
solver is a generator which yields all possible solutions. It is a good choice for programs like this because you can pause/cancel the solving as soon as you found the solution(s) you are looking for -
for sln in solver(mytree, 23):
print(sln)
The output isn't particularly interesting because each branch in mytree sums to 23 -
(12, 7, 4)
(12, 1, 10)
If we make anothertree with different values, we can see more interesting output -
anothertree = TreeNode \
( 1
, TreeNode(7, TreeNode(4), TreeNode(5))
, TreeNode(9, TreeNode(2), TreeNode(7))
)
for sln in solver(anothertree, 12):
print(sln)
(1, 7, 4)
(1, 9, 2)
I am trying to understand below recursion function which says whether a particular node exists in a binary tree. I did my homework and got most of the recursion part but the last return statement (return root.value == value or inleft or inright) bothers me.
can someone please help me in understanding this method?
def existsInTree(self, root, value):
if root is None:
return False
else:
inleft = self.existsInTree(root.left, value)
inright = self.existsInTree(root.right, value)
print(inleft,inright)
return root.value == value or inleft or inright
example binary tree:
10
/ \
11 9
we will first compare data of root with data of node to be searched. If the match is found, set the flag to true. Else, search the node in left subtree and then in the right subtree.
There's another way of looking at that return statement, you can split the return statement at the or keyword
def ifRootExists(self,root, value):
if (root == None):
return False
if (root.value == value):
return True
""" then recur on left sutree """
res1 = ifrootExists(root.left, value)
# root found, no need to look further
if res1:
return True
""" root is not found in left,
so recur on right subtree """
res2 = ifrootExists(root.right, value)
return res2
We can get the result from the above function whether some node exists.
The algorithm is as follows.
root is None or Not. If None, get back the position which the parent function called with the value of "False".
Otherwise, the function continuously search based on the current node.
inleft is a value of function "existsInTree" in which the current node's left child is the root node.
inright is a value of function "existsInTree" in which the current node's right child is the root node.
Let's assume that we want to search value as called V.
Which V exists in the tree means the current value is V or in the left tree, or in the right tree.
To summarize, inleft and inright means whether V includes or not in the subtree.
I'm trying to build a BST (binary search tree) with dict in python. I do not understand why my code is not adding nodes to the BST. I saw a similar post here:
How to implement a binary search tree in Python?
which looks the same as my code except declaring a node class, but I would like to know why my dict implementation fails (and hopefully improve my understanding of parameter passing with recursion in python).
keys = [10,9,2,5,3,7,101,18]
start = {'key': keys[-1], 'val': 1, 'left': None, 'right': None}
def binarySearch(root, node):
# compare keys and insert node into right place
if not root:
root = node
elif node['key'] < root['key']:
binarySearch(root['left'], node)
else:
binarySearch(root['right'], node)
# Now let's test our function and build a BST
while keys:
key = keys.pop()
node = {'key': key, 'val': 1, 'left': None, 'right': None}
binarySearch(start, node)
print(start) # unchanged, hence my confusion. Thx for your time!
===========================================
Edit: here is the code that would make it work!
def binarySearch(root, node):
# compare keys and insert node into right place
if not root:
root = node
elif node['key'] < root['key']:
if not root['left']: root['left'] = node
else: binarySearch(root['left'], node)
else:
if not root['right']: root['right'] = node
else: binarySearch(root['right'], node)
Here is what I think that is happening under the hood (why one version is able to add to BST but the other one is not):
In the original version, we will reach a recursion call where root still points to None inside the BST, but then root = node make root points to node which has absolutely no connection with start, i.e. the BST itself. Then local variables are deleted and no changes are made.
In the modified version, we will avoid this since when we add the node by e.g. root['left'] = node. Here root is still pointing to the original BST and thus we are modifying the key-val pair in the original BST instead of having root point to something totally outside the BST.
Let's run through your code as though we were the python interpreter.
Lets start at the first call: binarySearch(start, node)
Here start is the dict defined at the top of your script and node is another dict (which curiously has the same value).
Lets jump inside the call and we find ourselves at: if not root: where root refers to start above and so is truthy so fails this if.
Next we find ourselves at: elif node['key'] < root['key']: which in this case is not True.
Next we pass into the else: and we are at: binarySearch(root['right'], node).
Just before we jump into the first recursive call, lets review what the parameters to the call are: root['right'] from start has the value None and node is still the same dict which we want to insert somewhere. So, onto the recursive call.
Again we find ourselves at: if not root:
However this time root just refers to the first parameter of the first recursive call and we can see from the above review of the parameters that root refers to None.
Now None is considered falsy and so this time the if succeeds and we are on to the next line.
Now we are at root = node.
This is an assignment in python. What this means is that python will use the variable root to stop referring to None and to refer to whatever node currently refers to, which is the dict which was created in the while loop. So root (which is just a parameter, but you can think of as a local variable now) refers to a dict.
Now what happens is that we are at the end of the first recursive call and this function ends. Whenever a function ends, all the local variables are destroyed. That is root and node are destroyed. That is just these variables and not what they refer to.
Now we return to just after the first call site i.e. just after binarySearch(root['right'], node)
We can see here that the parameters: root['right'], node still refer to whatever they were referring to before. This is why your start is unchanged and why your program should deal with left and right now instead of recursing.
#Creted by The Misunderstood Genius
def add_root(e,key):
''''
e is node's name
key is the node's key search
'''
bst=dict()
bst[e]={'key':key,'P':None,'L':None,'R':None}
return bst
def root(tree):
for k,v in tree.items():
if v['P'] == None:
return k
def insert(tree, node, key):
tree[node]={'key':key,'P':None,'L':None,'R':None}
y =None
x = root(tree)
node_key = tree[node]['key']
while x is not None:
y=x
node_root=tree['R']['key']
if node_key < node_root:
x=tree[x]['L']
else:
x=tree[x]['R']
tree[node]['P']=y
if y is not None and node_key< tree[y]['key']:
tree[y]['L']=node
else:
tree[y]['R']=node
return tree
def print_all(tree):
for k,v in tree.items():
print(k,v)
print()
'''
Give a root node and key search target
Returns the name of the node with associated key
Else None
'''
def tree_search(tree,root, target):
if root ==None:
print(" key with node associate not found")
return root
if tree[root]['key'] == target:
return root
if target < tree[root]['key']:
return tree_search(tree,tree[root]['L'],target)
else:
return tree_search(tree,tree[root]['R'],target)
def tree_iterative_search(tree,root,target):
while root is not None and tree[root]['key']!=target:
if target < tree[root]['key']:
root=tree[root]['L']
else:
root=tree[root]['R']
return root
def minimum(tree,root):
while tree[root]['L'] is not None:
root=tree[root]['L']
return tree[root]['key']
bst=add_root('R',20)
bst=insert(bst,'M',10)
bst=insert(bst,'B',8)
bst=insert(bst,'C',24)
bst=insert(bst,'D',22)
bst=insert(bst,'E',25)
bst=insert(bst,'G',25)
print_all(bst)
print(tree_search(bst,'R',25))
x=tree_iterative_search(bst,'R',25)
print(x)
#print(minimum(bst,'R'))
I am very confused about the question No.426 on leetcode, as I think my answer is right. But after running it shows I am wrong. Below is the question and my original answer:
"""
# Definition for a Node.
class Node:
def __init__(self, val, left, right):
self.val = val
self.left = left
self.right = right
"""
class Solution:
def treeToDoublyList(self, root):
"""
:type root: Node
:rtype: Node
"""
if root:
sign = True
stack = []
node = root
while stack or node:
if node:
stack.append(node)
node = node.left
else:
node = stack.pop()
if sign:
pre,head = node, node
else:
pre.right = node
node.left = pre
pre = node
node = node.right
head.left = pre
pre.right = pre
return head
else:
return None
Could someone help me to figure out what's wrong with my codes? Any comment or suggestion will be so appreciated.
I see two problems with the code.
First is that inside your if sign: block you need to set sign = False since you only want to initialize head once and execute that block only the first time. (Not sure why the variable is called sign, perhaps first would be more appropriate, or just reusing a head = None for that condition would have worked too.)
The second bug is smaller and affects the last link in the list to make it circular. You want to set pre.right = head instead of pre, so that the last node of the list points back at the first, not at itself.
I haven't really tested this, so it's possible I'm missing something, but it looks to me that this should be enough to fix this code.