LeetCode 98: Validate Binary Search Tree - python

I have looked at this code line by line maybe 100 times and I am stumped. Why doesn't this work????
Problem: input of [5,4,6,null,null,3,7] (this is a BST with 5 being the root and 4 and 6 being its left and right nodes)
returns True when it should return False (3 should be not be to the right of parent node 5). False should be returned at the first nested if statement in the while loop.
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
# BFS method
current_node = root
queue = []
queue.append(current_node)
while len(queue) > 0:
current_node = queue.pop(0)
if current_node.left:
if (current_node.val > current_node.left.val) and (root.val > current_node.left.val):
queue.append(current_node.left)
else:
return False
if current_node.right:
if (current_node.val < current_node.right.val) and (root.val < current_node.right.val):
queue.append(current_node.right)
else:
return False
return True

The tree for which your code fails is:
5
/ \
4 6
/ \
3 7
When current_node is 6, your code checks that root.val > current_node.left.val, which means it checks that 5 > 3. This is true. And so it wrongly concludes there is no problem there, yet there is.
You may now think to just change the direction of that comparison, but there is a logical error in your approach: it is not enough to compare the root with every node in its subtree. For instance, in the following tree you should detect that 3 is violating the BST property, even though it is not in conflict with the root:
2
\
5
\
6
/
3
The conflict is with the value 5, which is neither the parent of 3, nor the root.
In conclusion, a BFS traversal is not really an ideal tool to verify a BST. Although it surely can be done, it is more natural to use DFS, as it allows to carry down the (accumulated) limitations that will apply to every node in the subtree.

Related

What is wrong in the code... Subtree of Another Tree

I am looking at LeetCode problem 572. Subtree of Another Tree:
Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise.
A subtree of a binary tree tree is a tree that consists of a node in tree and all of this node's descendants. The tree tree could also be considered as a subtree of itself.
Example 1
Input: root = [3,4,5,1,2], subRoot = [4,1,2]
Output: true
Example 2
Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
Output: false
My Code
class Solution:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
if not root and not subRoot:
return True
if root==None or subRoot==None:
return False
if root.val == subRoot.val and self.isSubtree(root.left,subRoot.left) and self.isSubtree(root.right,subRoot.right):
return True
return self.isSubtree(root.left,subRoot) or self.isSubtree(root.right,subRoot)
Input Failing:
Input
[3,4,5,1,null,2]
[3,1,2]
Output True
Expected False
The problem with your algorithm is that it allows the check to skip some nodes.
The failing test case represents these trees:
root: 3 subRoot: 3
/ \ / \
4 5 1 2
/ /
1 2
In the first call of your function, the roots have equal values, so then this recursive call is made:
root: 4 subRoot: 1
/
1
Now you can already see that here the subRoot is a sub tree of root, but this fact should not be used here, since we are half-way a tree matching procedure where the original roots already matched, so we are not allowed now to skip over the node 4, yet that is what will happen.
The real problem is that you are trying to cover two recursive needs in one recursive function:
find a node in root where subRoot matches with all its descendants
verify that all descendants match.
You need to split this into two independent recursive functions. For instance:
class Solution:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
def recurMatch(root, subRoot):
return not (subRoot or root) or (
subRoot and root and
root.val == subRoot.val and
recurMatch(root.left, subRoot.left) and
recurMatch(root.right, subRoot.right)
)
def recurSearch(root):
return root and (
recurMatch(root, subRoot) or
recurSearch(root.left) or
recurSearch(root.right)
)
return bool(recurSearch(root))
Faster?
The above solution will only perform "average" on LeetCode. You could improve by relying on fast string comparison: stringify both trees and check whether the second string occurs within the first. And the shorter the serialisation you can generate, the faster the algorithm will work.
Spoiler:
class Solution:
def isSubtree(self, root, subRoot):
def stringify(root):
return f"[{stringify(root.left) if root.left else ''}{chr(root.val+10128)}{stringify(root.right) if root.right else ''}]"
return stringify(subRoot) in stringify(root)

Minimum Element in Binary Search Tree

Given a Binary Search Tree. The task is to find the minimum element in this given BST.
Example 1:
Input:
5
/ \
4 6
/ \
3 7
/
1
Output: 1
Example 2:
Input:
9
\
10
\
11
Output: 9
Your Task:
The task is to complete the function minValue() which takes root as the argument and returns the minimum element of BST. If the tree is empty, there is no minimum element, so return -1 in that case.
My Code:
def minValue(root):
if root is None:
return -1
elif root.left is None:
return root.data
else:
minValue(root.left)
This code is giving me the output None with every testcase whereas if I change my minValue(root.left) in the else condition to return minValue(root.left), I'm getting correct answer. Can anyone tell me the reason as to why this is happening?
Every Python function returns something. If the function exits without a return then the returned value is None. Your code returns -1 if the first if statement is true. It returns root.data if the elif is true. Otherwise it returns None, since the else branch is taken and that returns nothing. It calls minValue but that doesn't actually do anything because you don't return anything there. After the function finishes the code just falls through to the end - and therefore Python returns None.
You haven't added a return statement in the second case, hence the output shows None as nothing was actually returned. Just change it to
def minValue(root):
if root is None:
return -1
elif root.left is None:
return root.data
else:
return minValue(root.left)

Binary Tree Search Chech Algorithm Python Not Working

I wrote this algorithm for a coding challenge on HackerRank to determine if a give binary tree is a BST. Yet in some cases when the tree is not a BST my algorithm returns True anyway. I couldn't find what was wrong, everything seems ok, right? Or is there something I don't know about BSTs?
Node is defined as:
class node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
My algorithm is
def checkBST(root):
if not root:
return True
else:
if root.left and root.left.data >= root.data:
return False
if root.right and root.right.data <= root.data:
return False
return checkBST(root.left) and checkBST(root.right)
A binary search tree has all the nodes on the left branch less than the parent node, and all the nodes on the right branch greater than the parent node.
So your code fails on a case like this:
5
/ \
4 7
/ \
2 6
It's not a valid BST because if you searched for 6, you'd follow the right branch from the root, and subsequently fail to find it.
Take a look at a picture below for which your code is giving an incorrect answer:
Where are you going wrong:
1. if an element exists on the left subtree if there exists a node with a value greater than root.
2. if an element exists on the right subtree if there exists a node with a value smaller than root.
You should try this approach :
if not root:
return True
else:
if root.left and maximumOfSubtree(root.left) >= root.data:
return False
if root.right and minimumOfSubtree(root.right) <= root.data:
return False
return checkBST(root.left) and checkBST(root.right)
so the problem is to determine whether the given tree is a BST or not.
the best way to find out is through an inorder traversal.
Do In-Order Traversal of the given tree and store the result in a temp array.
Check if the temp array is sorted in ascending order, if it is, then the tree is BST.
this can be the approach
def check_binary_search_tree_(root):
visited = []
def traverse(node):
if node.left: traverse(node.left)
visited.append(node.data)
if node.right: traverse(node.right)
traverse(root)
fc = {}
for i in visited:
if i in fc:
return False
else:
fc[i]=1
m = sorted(visited)
if visited==m:
return True
return False
refer to this for other methods https://www.geeksforgeeks.org/a-program-to-check-if-a-binary-tree-is-bst-or-not/
method 1 and method 2 are similar to your approach so it will help you to understand that too.

Checking if a treeNode is an ancestor of another node

def is_ancestor(node, middle):
# search down
if node.data == middle.data:
return True
if node.left:
is_ancestor(node.left, middle)
if node.right:
is_ancestor(node.right, middle)
return False
I am using this function to recursively check is a node is an ancestor of middle.
Let's say we have a tree that looks like
5
/
2
\
4
and I say that node is the node that points to 5 and middle is 2.
When calling is_ancestor(node_with_5, node_with_2), I am expecting to recursively move the node down both to left and right and return True whenever it finds the middle.
However, my current function gives me False even though it will find the middle in the first recursion call.
Any help?
You would have to make some little changes à la:
def is_ancestor(node, middle):
if node is middle: # data could coincide, compare nodes directly
return True
if node.left and is_ancestor(node.left, middle):
return True # do actually return something
if node.right and is_ancestor(node.right, middle):
return True # do actually return something
return False
You could get the entire logic in an even more concise way:
def is_ancestor(node, middle):
if node is None:
return False
if node is middle:
return True
return is_ancestor(node.left, middle) or is_ancestor(node.right, middle)

Size of subtree in Python

Almost every online tutorial I see on the subject when it comes to finding the size of a subtree involves calling a recursive function on each child's subtree.
The problem with this in Python is that it overflows if you recurse past a few hundred levels, so if I theoretically had a long, linear tree, it would fail.
Is there a better way to handle this? Do I need to use a stack instead?
Do I need to use a stack instead?
Sure, that's one way of doing it.
def iter_tree(root):
to_explore = [root]
while to_explore:
node = to_explore.pop()
yield node
for child in node.children:
to_explore.append(child)
def size(root):
count = 0
for node in iter_tree(root):
count += 1
return count
The stack would be the easiest non-recursive way of getting the size of the subtree (count of nodes under the given node, including the current node)
class Node():
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def subtree_size(root):
visited = 0
if not root: return visited
stack = [root]
while stack:
node = stack.pop()
visited += 1
if node.left: stack.append(node.left)
if node.right: stack.append(node.right)
return visited
You can mirror the recursive algorithm using a stack:
numNodes = 0
nodeStack = [(root,0)] # (Node,0 means explore left 1 means explore right)
while nodeStack:
nextNode, leftOrRight = nodeStack.pop()
if not nextNode: #nextNode is empty
continue
if leftOrRight == 0:
numNodes += 1
nodeStack.append((nextNode,1))
nodeStack.append((nextNode.leftChild,0))
else:
nodeStack.append((nextNode.rightChild,0))
print(numNodes)
Some things to notice: This is still a Depth-first search! That is, we still fully explore a subtree before starting to explore the other. What this means to you is that the amount of additional memory required is proportional to the height of the tree and not the width of the tree. For a balanced tree the width of the tree is 2^h where h is the height of the tree. For a totally unbalanced tree the height of the tree is the number of nodes in the tree, whereas the width is one! so it all depends on what you need :)
Now It is worth mentioning that you can make a potential optimization by checking if one of the subtrees is empty! We can change the body of if leftOrRight == 0: to:
numNodes += 1
if nextNode.rightChild: #Has a right child to explore
nodeStack.append((nextNode,1))
nodeStack.append((nextNode.leftChild,0))
Which potentially cuts down on memory usage :)

Categories

Resources