This problem asks to find the largest subtree within a given binary tree that is also a Binary Search Tree (BST). The solution uses a post-order Depth-First Search (DFS) approach with a divide and conquer strategy. The key idea is to process the subtrees recursively and check if combining them with the current node still results in a valid BST.
The core logic resides in the dfs
function (or its equivalent in different languages). For each node, it recursively calls dfs
on its left and right children. The returned values from the recursive calls represent:
lmi
(or left[0]
): The minimum value in the left subtree.lmx
(or left[1]
): The maximum value in the left subtree.ln
(or left[2]
): The size (number of nodes) of the largest BST subtree rooted at the left child.The same applies to rmi
, rmx
, and rn
for the right subtree.
The function then checks the following condition:
lmx < root.val < rmi
This condition ensures that the current node's value is greater than all values in its left subtree and smaller than all values in its right subtree, a requirement for a BST.
If the condition is true, it means the subtree rooted at the current node is a valid BST. The size of this BST is ln + rn + 1
(including the current node). The global variable ans
(or equivalent) keeps track of the largest BST size encountered so far.
If the condition is false, it means the subtree rooted at the current node is not a valid BST, and the function returns (-inf, inf, 0)
(or equivalent) to indicate this.
Time Complexity: O(N), where N is the number of nodes in the tree. This is because each node is visited exactly once during the DFS traversal.
Space Complexity: O(H), where H is the height of the tree. This is due to the recursive call stack used during the DFS. In the worst-case scenario (a skewed tree), H could be equal to N, resulting in O(N) space complexity. However, in a balanced tree, H would be log(N), leading to O(log N) space complexity.
The Python code demonstrates the algorithm effectively:
def largestBSTSubtree(self, root: Optional[TreeNode]) -> int:
def dfs(root):
if root is None:
return inf, -inf, 0 # Return infinity, negative infinity, and 0 for empty subtree
lmi, lmx, ln = dfs(root.left) # recursive call for left
rmi, rmx, rn = dfs(root.right) #recursive call for right
nonlocal ans
if lmx < root.val < rmi: # checking BST condition
ans = max(ans, ln + rn + 1) # updating the largest BST size
return min(lmi, root.val), max(rmx, root.val), ln + rn + 1 # return min,max and size
return -inf, inf, 0 # return if not BST
ans = 0
dfs(root)
return ans
The other languages (Java, C++, Go) follow a very similar structure, adapting the syntax and data structures to each language's specifics, but maintain the core logic of the algorithm.