I am looking at LeetCode problem 98. Validate Binary Search Tree:
Given the root of a binary tree, determine if it is a valid binary search tree (BST).
A valid BST is defined as follows:
The left subtree of a node contains only nodes with keys less than the node's key.
The right subtree of a node contains only nodes with keys greater than the node's key.
Both the left and right subtrees must also be binary search trees.
What is the problem with the below provided code for validating the binary tree property with preorder traversal?
# 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 isValidBST(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
def preorder(root):
if root.left!=None:
if root.left < root.val:
preorder(root.left)
else:
return False
if root.right!=None:
if root.right>root.val:
preorder(root.right)
else:
return False
t= preorder(root)
return t!=False
It is returning False for the Test case where root=[2,1,3]
You need to traverse the right-sub-tree only when the left-sub-tree is a valid one.
def preorder(root):
if root.left!=None:
if root.left < root.val:
preorder(root.left)
else:
return False
if root.right!=None (and left-sub-tree is valid):
if root.right>root.val:
preorder(root.right)
else:
return False
Note: I don't know Python. Understand the comment while checking right-sub-tree.
Several issues:
root.left < root.val is comparing a node with an integer. This should be root.left.val < root.val. Same for the right side check.
Although preorder returns False when there is a violation, the caller that makes the recursive call ignores this return value and happily continues. This is wrong. It should stop the traversal when the recursive call returns False, and itself return False to its caller.
Not the main issue, but as preorder returns False, it should better return True in the remaining cases, so you can be sure that preorder returns a boolean, and don't have to treat None or compare the return value explicitly with False.
The algorithm only checks whether a direct child of a node has a value that is not conflicting with the value of the parent, but that is not enough for validating a BST. All the values in the left subtree must be less than parent's value, not just the left child. So even if the above problems are fixed, this algorithm will wrongly say this BST is valid:
4
/
1
\
9 <-- violation (because of 4)
To solve the last point, you need to revise the algorithm: realise that when you are deep in a BST, there is a window of values that a subtree in a valid BST can have: there is a lower limit and an upper limit. In the above example the right child of the node 1 could only have a value in the range (1, 4).
Here is an implementation as a spoiler:
class Solution(object):
def isValidBST(self, root):
def preorder(root, low, high):
return not root or (
low < root.val < high and
preorder(root.left, low, root.val) and
preorder(root.right, root.val, high)
)
return preorder(root, -float("inf"), float("inf"))
Related
I am working on LeetCode problem 111. Minimum Depth of Binary Tree:
Given a binary tree, find its minimum depth.
The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node.
Note: A leaf is a node with no children.
I have used a breadth first algorithm and tried to change it to align it to the problem. But the function is returning None.
Could anyone explain why that happens?
def minDepth(self, root: Optional[TreeNode]) -> int:
queue=[]
if root==None:
return 0
level=1
while(len(queue)>0):
n=len(queue)
for i in range(n):
node=queue.pop(0)
if not node.right and not node.left:
return(level)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
level+=1
The problem is that your queue is empty at the moment the loop is about to start.
You should place the root node in it:
if root is None:
return 0
queue = [root] # <----
level = 1
while queue:
# ...etc
Im attempting to write an algorithm that validates a binary search tree. This is to answer the following algorithm question: https://leetcode.com/problems/validate-binary-search-tree/. I've opted to use a recursive approach and have coded the following in Python3:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
left = self.isValidBST(root.left)
right = self.isValidBST(root.right)
if left and right:
if (root.left and root.val <= root.left.val) or (root.right and root.val >= root.right.val):
return False
return True
return False
The algorithm above works up to the 71st test case provided by the leetcode platform. However, it fails on the following input:
[5,4,6,null,null,3,7]
The expected output is False (i.e. not a binary search tree). However, my algorithm outputs True. I've consulted the question description and the diagrams provided multiple times and I believe my output to be correct. With that in mind, is there something I'm missing? Or is the platform incorrect?
The input [5,4,6,null,null,3,7] has the following tree representation.
This is clearly not a valid binary search tree, since the node 3 is less than it's grand parent 5.
In a binary search tree, every node to the right (including all ancestors), must be greater (or grater or equal depending on your definition). The same goes for the left sub trees. Please note that the leetcode problem in this case specifically states that they must be less and greater so equal is not valid. Think about it this way: You need to be able to do a binary search in a BST. When you go right, you expect all the nodes there to be greater than the parent. If a node less than the parent is found there, the ordering is broken and you can't do a binary search. You need to search linearly. Binary searching for 3 in this example would return false, while it's clearly there. So, it isn't a valid binary search tree.
Your algorithm only checks whether the immediate left and right children respect this condition.
To correct it, you need to pass down the min and max allowed values to the recursive function. When you go left, you set the max value equal to the current node value. When you go right you set the min value to the current node value. This makes sure that, say the nodes to the left of a node are never greater than that node.
A possible implementation is as follows:
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
return self.isValidBSTHelper(root, None, None)
def isValidBSTHelper(self, root: TreeNode, min_val: int, max_val: int)-> bool:
if root == None: return True
if (min_val != None and root.val <= min_val) or (max_val != None and root.val >= max_val):
return False
return self.isValidBSTHelper(root.left, min_val, root.val) \
and self.isValidBSTHelper(root.right, root.val, max_val)
basically i am required to come out with a pseudocode for this. What i currently have is
dictionary = {}
if node.left == none and node.right == none
visit(node)
dictionary[node] = 1
This is only the leaf nodes, how do i get the size for each node(parent and root)?
You can do a post-order traversal to find the size of each node.
The idea is to first handle both left and right trees. Then, after they are processed - you can use this data to process the current node.
This should look something like:
count = 0
if (node.left != none)
count += visit(node.left)
if (node.right != none)
count += visit(node.right)
// self is included.
count += 1
// update the node
node.size = count
return count
The dictionary for visited nodes is not needed since this is a tree, it guarantees to end.
As a side note - the size attribute of each node, is an important one. It basically upgrades your tree to a Order Statistics Tree
well the concept is that each node will know it's subtree size by first knowing the subtree size of all it's child which is maximum two child here as it is a binary tree, so once it knows subtree size of all child it can then add up all of them and atlast add 1 to it's
result and then the same thing will be done by it's parent also and so on upto root node. if we think about leaf node, it
has no child, so result subtree size will be only 1 in which it include itself.
one this idea is clear, it is easy to write code
that while traversing we will first know the subtree size of child nodes of current node then add 1 in it, in case of leaf node it will have subtree size of 1 only, below is the pseudocode of traverse funtion which finds the subtree size of each node and store them in dictionary sizeDictionary and a visited dictionary/array having larger scope has been used to keep track of visited nodes.
traverse(Tree curNode, dictionary subTreeSizeDictionary)
visited[curNode] = true
subtreeSizeDictionary[curNode] = 0
for child of curNode
if(notVisited[child])
traverse(child , sizeDictionary)
subtreeSizeDictionary[curNode] += subtreeSizeDictionary[child]
subtreeSizeDictionary[curNode] += 1;
here it is binary tree, but as you can see from pseudocode this concept can be used for any valid tree, the time complexity is O(n) as we visited each node only once.
I would like to know if Red-Black Trees are considered Full Binary Trees because of the dummy nodes. I know there are 'NIL' nodes which are black and CLRS picture seems to indicate their are two such 'NIL' nodes for ever red node on the first level of a general RBT, however I am still unclear about whether these node contribute to the "fullness" of the tree.
In addition, are these NIL nodes usually implemented with a standard BST which initializes each Tree-Node as a BST object:
class BinaryTree:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
or
class BST:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
I'm working on better understanding the application of a depth-first search algorithm. I understand how to use it to traverse a binary search tree to produce a sorted list. My Python implementation looks like this:
class bst_node:
def __init__(self, x):
self.x = x
self.left = None
self.right = None
def get_dfs_path(bst_node):
""" Returns a depth-first search path for a BST """
left = [] if bst_node.left == None else get_dfs_path(bst_node.left)
right = [] if bst_node.right == None else get_dfs_path(bst_node.right)
return left + [bst_node] + right
Which works quite nicely. I'm struggling to understand, however, whether this algorithm can be meaningfully applied to a digraph in general, rather than the more strict BST. Consider the following digraph node implementation:
class di_node:
def __init__(self, x):
self.x = x
self.visited = False
self.children = []
Since a node in a digraph can have an arbitrary number of children, the dfs logic can't simply construct the path as dfs_path(left) + parent_node + dfs_path(right). Can someone help me understand if/how dfs applies to a digraph?
EDIT
Ok, based on the responses let me attempt a dfs traversal for a di_node. Please let me know if I'm anywhere close to the mark:
def get_dfs_path(di_node):
""" Returns a depth-first search path for a digraph """
if di_node.visited:
return []
else:
di_node.visited = True
return [di_node] + [ get_dfs_path(child) for child in di_node.children) ]
As you noticed in-order traversal (left-subtree, current node, right subtree) doesn't make much sense for general graphs since a node can have more than two subtrees. However a depth first search can also use pre-order (process the current node first, then the subtrees) or post-order (first process the subtrees, then the current node) traversal. Those two work just fine with graphs.
One thing you have to keep track of when performing DFS on graphs is which nodes you already visited. Otherwise you'd get infinite loops when traversing cyclic graphs.
You can use DFS in a graph to detect cycles. Keep track of visited nodes and if you visit a node that was already visited then you have detected a cycle.