I have a very simple Binary Tree
class TreeNode(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
root = TreeNode(8)
root.left = TreeNode(5)
root.right = TreeNode(14)
root.left.left = TreeNode(4)
root.left.right = TreeNode(6)
root.left.right.left = TreeNode(8)
root.left.right.right = TreeNode(7)
root.right.right = TreeNode(24)
root.right.right.left = TreeNode(22)
and I implemented a function to find the closest number in the tree to the target (19):
def closest_value(root, target, closest=0):
if abs(root.val - target) < abs(closest - target):
closest = root.val
print(closest)
if root.left is not None:
closest_value(root.left, target, closest)
if root.right is not None:
closest_value(root.right, target, closest)
return closest
The result should be obviously 22, but instead i get 8. Surprisungly, when I print all the following 'closest' numbers, the function seems to be working fine: It prints: 8, 14, 22. But why doesn't it return the latest clostest number: 22?
result = closest_value(root, 19)
print('result:', result)
The value of closest in the first call to closest_value is not updated in the if-statements. Simply assign the value to closest:
def closest_value(root, target, closest=0):
if abs(root.val - target) < abs(closest - target):
closest = root.val
if root.left is not None:
#assign value
closest = closest_value(root.left, target, closest)
if root.right is not None:
#assign value
closest = closest_value(root.right, target, closest)
return closest
result = closest_value(root, 19)
print(result)
# 22
You are not using the result of your recursive calls to determine the final returned value.
Perhaps a simpler approach, without pushing down a defaulted parameter would be easier:
def closest(node,value):
if not node: return float('inf')
vals = [node.val, closest(node.left,value), closest(node.right,value)]
return min(vals,key=lambda v:abs(v-value))
closest(root,19) # 22
One issue, is that this is an O(n) approach that will go through the whole binary tree without leveraging the hierarchy. For a sorted binary tree, you can get a O(logN) solution, by implementing a pair of binary search functions to find the closest node with a value that is <= and the closest node with a value that is >=. Then only apply the absolute value comparison between these two nodes that will have been found in O(logN) time.
def findLE(node,value):
if not node: return None
if node.val == value: return node
if node.val<value: return findLE(node.right,value) or node
return findLE(node.right,value)
def findGE(node,value):
if not node: return None
if node.val == value: return node
if node.val>value: return findGE(node.left,value) or node
return findGE(node.right,value)
def closestValue(node,value):
less = findLE(node,value)
more = findGE(node,value)
if more and less:
return min(more.val,less.val,key=lambda v:abs(v-value))
return (more or less).val
Note that your binary tree is not in sorted order because of the 8 that is left of node 6:
8
__/ \_
5 14
/ \ \
4 6 24
/ \ /
8 7 22
(you can find the binary tree printing function here)
Related
I'm solving the LeetCode problem 235. Lowest Common Ancestor of a Binary Search Tree:
Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST.
According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”
My solution I ran in local interpreter is below.
class TreeNode(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
root = TreeNode(6)
root.left = TreeNode(2)
root.right = TreeNode(8)
root.left.left = TreeNode(0)
root.left.right = TreeNode(4)
root.left.right.left = TreeNode(3)
root.left.right.right = TreeNode(5)
root.right.left = TreeNode(7)
root.right.right = TreeNode(9)
p = 2
q = 8
class Solution(object):
def lowestCommonAncestor(self, root, p, q):
def pathFind(path, node, target):
path.append(node)
if node.val == target:
return path
elif node.val < target:
return pathFind(path, node.right, target)
elif node.val > target:
return pathFind(path, node.left, target)
else:
return None
path_p = pathFind([], root, p)
path_q = pathFind([], root, q)
idx = 0
while True:
if path_p[idx] == path_q[idx]:
idx += 1
else:
break
return path_p[idx - 1]
print(Solution().lowestCommonAncestor(root, p, q))
My code works well for many test cases but, in submission in LeetCode, it does not pass even the base case that I passed in my local interpreter.
For example, when I set p = 2 and q = 8 (base case), with the sample BST I constructed in the code above, LeetCode rejects my solution.
class Solution(object):
def lowestCommonAncestor(self, root, p, q):
def pathFind(path, node, target):
path.append(node)
if node.val == target:
return path
elif node.val < target:
return pathFind(path, node.right, target)
elif node.val > target:
return pathFind(path, node.left, target)
else:
return None
path_p = pathFind([], root, p)
path_q = pathFind([], root, q)
idx = 0
while True:
if path_p[idx] == path_q[idx]:
idx += 1
else:
break
return path_p[idx - 1]
What am I missing here?
There are these issues:
A misunderstanding: the parameters p and q are not values, but nodes. The code challenge description clearly states "two given nodes". So when calling pathFind make sure to pass p.val and q.val as last argument.
When p happens to be an ancestor of q (or vice versa), the final loop will not find a node that is different, but run in a "list index out of range" error. There needs to be a check whether the index is still valid in both lists, if not, return the previous node. One way to achieve this is to use zip(path_p, path_q) as that will only visit pairs.
Corrected code:
class Solution:
def lowestCommonAncestor(self, root, p, q):
def pathFind(path, node, target):
path.append(node)
if node.val == target:
return path
elif node.val < target:
return pathFind(path, node.right, target)
elif node.val > target:
return pathFind(path, node.left, target)
else:
return None
path_p = pathFind([], root, p.val)
path_q = pathFind([], root, q.val)
for idx, (a, b) in enumerate(zip(path_p, path_q)):
if a != b:
return path_p[idx - 1]
return path_p[idx]
I want to print a Binary Tree using Inorder Traversal, I have these functions and I'm wondering how I would go about writing a function to use them to print the binary tree. Any help massively appreciated.
def inorder(self):
if not self.is_empty():
for p in self._subtree_inorder(self.root()):
yield p
def _subtree_inorder(self, p):
if self.left(p) is not None:
for other in self._subtree_inorder(self.left(p)):
yield other
yield p
if self.right(p) is not None:
for other in self._subtree_inorder(self.right(p)):
yield other
def positions(self):
return self.inorder()
Here two possible solutions in Python, given the following binary search tree.
20
/ \
10 30
/ \
35 40
\
37
Recursive traversal
The recursion ends whenever a node is null.
Call inorder_rec() first for the left subtree, then print the value of the current node, then print it for the right subtree.
Traversal using generator
The approach is more or less the same (not surprisingly, as the algorithm is the same). We first need to yield all the result from the left subtree, then we yield the value of the current node and last but not least we yield the keys from the right subtree.
All together
class Node:
def __init__(self, key):
self.left = None
self.right = None
self.key = key
class Bst:
def __init__(self):
self.root = None
def insert(self, key):
self.root = self.insert_rec(self.root, key)
def insert_rec(self, node, key):
if node is None:
return Node(key)
if key == node.key:
print(f"Key {key} already present! Ignoring value!")
return node
if key <= node.key:
node.left = self.insert_rec(node.left, key)
else:
node.right = self.insert_rec(node.right, key)
return node
def inorder(self):
self.inorder_rec(self.root)
def inorder_rec(self, node):
# end of recursion if current node is None
if node is None:
return
# indorder means: left subtree, then own value, then right subtree
self.inorder_rec(node.left)
print(node.key)
self.inorder_rec(node.right)
def inorder_with_generator(self):
# yield from inorder_genreator()
yield from self.inorder_generator(self.root)
def inorder_generator(self, node):
# nothing to yield if node is None
if node is not None:
for node_data in self.inorder_generator(node.left):
yield node_data
yield node.key
for node_data in self.inorder_generator(node.right):
yield node_data
tree = Bst()
tree.insert(20)
tree.insert(10)
tree.insert(30)
tree.insert(40)
tree.insert(35)
tree.insert(37)
tree.inorder()
print(list(tree.inorder_with_generator()))
Expected output:
10
20
30
35
37
40
[10, 20, 30, 35, 37, 40]
To avoid having to provide the root node as an argument every time I have added two functions which always start the traversal at the root node without the need to supply any parameters.
I'm trying to write code to find the minimum depth of a binary tree.
https://leetcode.com/problems/minimum-depth-of-binary-tree/
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def minDepth(self, node):
if node is None:
return 0
else :
# Compute the depth of each subtree
lDepth = self.minDepth(node.left)
rDepth = self.minDepth(node.right)
return min(lDepth, rDepth) + 1
However, this solution does not work on some test cases, such as a highly unbalanced binary tree, which devolves to a linked list (ex [2, None, 3, None, 4, None, 5, None, 6]
The minimum depth is 5 (as None children do not count.) However, my solution returns 1, so it must be treating the left child of 2 as the minimum depth leaf node.
2
/ \
3
/ \
4
/ \
5
/ \
6
I'm at a loss, why does this solution not adequately address this use case?
I think its because of the min function. Starting from 2, your code checks the left which is 0, then checks right which is recursively checked and returns 4. However, you call min(0, 4)+1 which gives you an output of 1.
Your code stops recursion on None which is not a TreeNode, obviously, the term "depth" is undefined for such objects. Try to stop your recursion on so-called "leafs": nodes without children. Check out my solution for this problem:
def is_leaf(node: TreeNode):
return node.left is None and node.right is None
def min_depth(root: TreeNode):
if is_leaf(root):
return 1
not_none_children = (
child if child is not None
for child in (root.left, root.right)]
)
return min(min_depth(child) for child in not_none_children) + 1
#Avinash, thanks for ID'ing the edge case.
And the problem #kellyBundy https://leetcode.com/problems/minimum-depth-of-binary-tree/submissions/
class Solution(object):
def minDepth(self, node):
# no node (dead branch)
if node is None:
return 0
lDepth = self.minDepth(node.left)
rDepth = self.minDepth(node.right)
if node.left and node.right:
return min(lDepth, rDepth) + 1
else: #prevents counting first dead branch as minDepth
return max(lDepth, rDepth) + 1
This is the answer for leet code question 270(Find Closest Value in BST) in Python. I could'nt grasp what and how the tree.value in this code works.
def findClosestValueInBst(tree, target):
return findClosestValueInBstHelper(tree, target, closest)
def findClosestValueInBstHelper(tree, target, closest):
if tree is None:
return Closest
if abs(target - closest) > abs(target - tree.value):
closest = tree.value
if target < tree.value:
return findClosestValueInBstHelper(tree.left, target, closest)
elif target > tree.vlaue:
return findClosestValueInBstHelper(tree.right, target, closest)
else:
return closest
There is a class for TreeNode that's defined by LeetCode, and you don't have to add that to the Solution:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
This'd pass through:
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def closestValue(self, root, target):
a_value = root.val
child = root.left if target < a_value else root.right
if not child:
return a_value
b_value = self.closestValue(child, target)
return min((b_value, a_value), key=lambda x: abs(target - x))
Also, there is no tree.value, it should be tree.val I guess.
References
For additional details, you can see the Discussion Board. There are plenty of accepted solutions with a variety of languages and explanations, efficient algorithms, as well as asymptotic time/space complexity analysis1, 2 in there.
I wrote this code and when I use print I see that I get the leaves. However, the final return from the function is None and not the sum of the leaves, which is supposed to be 7 in this example. I'd be happy to know whats wrong here. Thank you !
class Node:
def __init__(self, val=None):
self.left = None
self.right = None
self.val = val
def sum_leafs(tree):
if tree is None:
return 0
if tree.right and tree.left:
sum_leafs(tree.right)
sum_leafs(tree.left)
elif tree.right or tree.left:
if tree.right:
sum_leafs(tree.right)
elif tree.left:
sum_leafs(tree.left)
elif tree.right is None and tree.left is None:
return sum_leafs(tree.left) + 1
node = Node(10)
node.right = Node(2)
node.left = Node(11)
node.left.right = Node(5)
print(sum_leafs(node))
You forgot to add + when you sum the branches (left/right) and also you forgot to access val which is the most crucial thing for the whole thing to work.
Further, the logic can be simplified:
def sum_leafs(tree):
if tree is None:
return 0
if not tree.right and not tree.left:
return tree.val
return sum_leafs(tree.right) + sum_leafs(tree.left)
You are not adding the sums together or returning them. This can also be done with a method in the class:
class Node:
def __init__(self, val=None):
self.left = None
self.right = None
self.val = val
def sum(self):
s = 0
if self.left is not None:
s += self.left.sum()
if self.right is not None:
s += self.right.sum()
return self.val + s
node = Node(10)
node.right = Node(2)
node.left = Node(11)
node.left.right = Node(5)
print(node.sum())
returns:
28
You are not properly returning the calculated leaf sums. Try this:
class Node:
def __init__(self, val=None):
self.left = None
self.right = None
self.val = val
def sum_leafs(tree):
if tree is None:
return 0
elif tree.right and tree.left:
return sum_leafs(tree.right) + sum_leafs(tree.left)
elif tree.right or tree.left:
if tree.right:
return sum_leafs(tree.right)
elif tree.left:
return sum_leafs(tree.left)
elif tree.right is None and tree.left is None:
return tree.val
node = Node(10)
node.right = Node(2)
node.left = Node(11)
node.left.right = Node(5)
print(sum_leafs(node))
7
node
First I'm going to update your Node interface so that it's possible to set left and right branches when creating nodes -
class Node:
def __init__(self, val=None, left=None, right=None):
self.left = left
self.right = right
self.val = val
This allows us to create tress more ergonomically, such as -
t = Node(10, Node(11, None, Node(5)), Node(2))
traverse
Now we write a generic traverse procedure. This allows us to separate 1) the traversal of our tree from 2) the intended operation we want to perform on each tree element -
def traverse(tree):
if tree is None:
return
else:
yield tree.val
yield from traverse(tree.left)
yield from traverse(tree.right)
Now the need for sum_leafs disappears. We have decoupled traversal logic from summing logic. We can calculate the sum of leafs with a simple combination of sum and traverse -
print(sum(traverse(t)))
# 28
don't repeat yourself
Or, instead of summing the values, we could write a search function to find the first value that passes a predicate -
def search(test, tree):
for val in traverse(tree):
if test(val):
return val
print(search(lambda x: x < 10, t))
# 5
print(search(lambda x: x > 99, t))
# None
Or, we could simply collect each value into a list -
print(list(traverse(t)))
# [ 10, 11, 5, 2 ]
As you can see, removing the traversal logic from each function that depends on our tree can be a huge help.
without generators
If you don't like generators, you can write the eager version of traverse which always returns a list. The difference now is there is no way to partially traverse the tree. Note the similarities this program shares with the generator version -
def traverse(t):
if t is None:
return [] # <-- empty
else:
return \
[ t.val
, *traverse(t.left) # <-- yield from
, *traverse(t.right) # <-- yield from
]
print(traverse(t))
# [ 10, 11, 5, 2 ]