Given the root of a binary tree, find the largestĀ subtree, which is also a Binary Search Tree (BST), where the largest means subtree has the largest number of nodes.
AĀ Binary Search Tree (BST)Ā is a tree in which all the nodes follow the below-mentioned properties:
- The left subtree values are less than the value of their parent (root) nodeās value.
- The right subtree values are greater than the value of their parent (root) nodeās value.
Note:Ā A subtree must include all of its descendants.
solutions
preorder recursion w/ validation
We can first check to see if the BST rooted at root is a valid BST (we can do this by propagating valid left and right bounds down the recursion).
The issue with this preorder recursion is that we do a lot of repeated work, as the first call checks every node, but each subsequent call has to recalculate all of those nodes.
# O(n)
def is_valid_bst(self, node, l, r) -> bool:
if not node:
return True
return (
l < node.val < r
and self.is_valid_bst(node.left, l, node.val)
and self.is_valid_bst(node.right, node.val, r)
)
# O(n)
def count_nodes(self, node):
if not node:
return 0
return 1 + self.count_nodes(node.left) + self.count_nodes(node.right)
# best case: O(nlogn), worst case: O(n^2)
def largestBSTSubtree(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
# If current subtree is a valid BST, its our best answer.
if self.is_valid_bst(root, -inf, inf):
return self.count_nodes(root)
# Find BST in left and right subtrees of current nodes.
return max(
self.largestBSTSubtree(root.left),
self.largestBSTSubtree(root.right)
)postorder validation
To save on computation, we should start by computing the nested values first. For example, if we already know that a nodeās left and right children are valid BSTās (as well as their ranges), we can check whether node is a valid BST in constant time.
This is very similar in logic to the postorder optimization reached in 427-construct-quad-tree.
# O(n) - we only traverse each node once
def largestBSTSubtree(self, root: Optional[TreeNode]) -> int:
def helper(node):
if not node:
# min = inf, max = -inf
# this ensures it's a valid child for any node
return 0, inf, -inf
left_size, left_min, left_max = helper(node.left)
right_size, right_min, right_max = helper(node.right)
# node is a valid BST
if left_max < node.val < right_min:
# handle leaf workaround, if left_min is inf or right_max is -inf, use root.val
return 1+left_size+right_size, min(node.val, left_min), max(node.val, right_max)
# node is not a valid BST
# largest BST in node's subtree is best of left or right recursion
# if neither left or right are valid BST, the recursion keeps going down
# min = -inf, max = inf -- invalid as child for any node
return max(left_size, right_size), -inf, inf
ans, _, _ = helper(root)
return ans