{x}
blog image

Largest BST Subtree

Solution Explanation:

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.

Algorithm:

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 and Space Complexity Analysis:

  • 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.

Code Explanation (Python):

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.