Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

333. Largest BST Subtree - Leetcode Solution

Code Implementation

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def largestBSTSubtree(self, root: TreeNode) -> int:
        self.max_bst = 0

        def helper(node):
            if not node:
                return (True, 0, float('inf'), float('-inf'))
            left_is_bst, left_size, left_min, left_max = helper(node.left)
            right_is_bst, right_size, right_min, right_max = helper(node.right)
            if left_is_bst and right_is_bst and left_max < node.val < right_min:
                size = left_size + right_size + 1
                self.max_bst = max(self.max_bst, size)
                return (True, size, min(left_min, node.val), max(right_max, node.val))
            else:
                return (False, 0, 0, 0)
        helper(root)
        return self.max_bst
      
// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
public:
    int max_bst = 0;
    // Returns tuple: isBST, size, min, max
    std::tuple helper(TreeNode* node) {
        if (!node)
            return std::make_tuple(true, 0, INT_MAX, INT_MIN);
        auto [left_bst, left_size, left_min, left_max] = helper(node->left);
        auto [right_bst, right_size, right_min, right_max] = helper(node->right);
        if (left_bst && right_bst && left_max < node->val && node->val < right_min) {
            int size = left_size + right_size + 1;
            max_bst = std::max(max_bst, size);
            int min_val = std::min(left_min, node->val);
            int max_val = std::max(right_max, node->val);
            return std::make_tuple(true, size, min_val, max_val);
        }
        return std::make_tuple(false, 0, 0, 0);
    }
    int largestBSTSubtree(TreeNode* root) {
        helper(root);
        return max_bst;
    }
};
      
// Definition for a binary tree node.
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

class Solution {
    int maxBST = 0;
    // Returns int[]: {isBST:1/0, size, min, max}
    private int[] helper(TreeNode node) {
        if (node == null)
            return new int[]{1, 0, Integer.MAX_VALUE, Integer.MIN_VALUE};
        int[] left = helper(node.left);
        int[] right = helper(node.right);
        if (left[0] == 1 && right[0] == 1 && left[3] < node.val && node.val < right[2]) {
            int size = left[1] + right[1] + 1;
            maxBST = Math.max(maxBST, size);
            int min = Math.min(left[2], node.val);
            int max = Math.max(right[3], node.val);
            return new int[]{1, size, min, max};
        }
        return new int[]{0, 0, 0, 0};
    }
    public int largestBSTSubtree(TreeNode root) {
        helper(root);
        return maxBST;
    }
}
      
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
var largestBSTSubtree = function(root) {
    let maxBST = 0;
    function helper(node) {
        if (!node) return [true, 0, Infinity, -Infinity];
        let [lBST, lSize, lMin, lMax] = helper(node.left);
        let [rBST, rSize, rMin, rMax] = helper(node.right);
        if (lBST && rBST && lMax < node.val && node.val < rMin) {
            let size = lSize + rSize + 1;
            maxBST = Math.max(maxBST, size);
            return [true, size, Math.min(lMin, node.val), Math.max(rMax, node.val)];
        } else {
            return [false, 0, 0, 0];
        }
    }
    helper(root);
    return maxBST;
};
      

Problem Description

You are given the root of a binary tree. Your task is to find the size (number of nodes) of the largest subtree that is a Binary Search Tree (BST). A subtree is any node and all its descendants in the tree. A BST is defined as a binary tree in which for every node, all values in its left subtree are less than the node's value, and all values in its right subtree are greater than the node's value.

  • The input is a binary tree represented by its root node.
  • You must return an integer: the size of the largest BST subtree found within the given tree.
  • Each node can only be used once in the subtree (no reuse).
  • There is always at least one node in the tree.

Thought Process

To solve this problem, we first need to understand that not every subtree in a binary tree is a BST. The naive approach would be to check every possible subtree and verify if it is a BST, then keep track of the largest one. However, this would be very inefficient, especially for large trees.

We can improve on this by using recursion. For each node, we can recursively determine whether its left and right subtrees are BSTs, and if so, whether combining them with the current node still satisfies the BST property. If it does, we can calculate the size of this BST. If not, we keep the maximum size found in any of its subtrees.

The key insight is that for each node, we need to know:

  • Whether its left and right subtrees are BSTs
  • The size of the largest BST in its left and right subtrees
  • The minimum and maximum values in its left and right subtrees (for BST validation)
By collecting and propagating this information up the tree during a post-order traversal, we can efficiently determine the answer.

Solution Approach

We use a recursive, bottom-up (post-order) traversal of the tree. At each node, we gather information from its left and right children. Here are the steps:

  1. Base Case: If the node is null, we return that it is a BST of size 0, with min=+∞ and max=−∞ (so it doesn't affect parent comparisons).
  2. Recursive Step: For each node, recursively check its left and right children, collecting:
    • Whether the subtree is a BST
    • The size of the subtree if it is a BST
    • The minimum and maximum values in the subtree
  3. BST Validation: If both left and right subtrees are BSTs, and the current node's value is greater than the max of the left subtree and less than the min of the right subtree, then the subtree rooted at this node is also a BST.
  4. Update Maximum: Whenever we find a BST, we update our global maximum if the size is larger than what we've seen before.
  5. Return Values: Each recursive call returns whether the subtree is a BST, its size, its minimum, and its maximum.

This approach ensures that each node is visited only once, and all necessary information is passed up the tree efficiently.

Example Walkthrough

Let's consider the following binary tree:

        10
       /  \
      5   15
     / \    \
    1   8    7
  
  1. Start at the leaves. Nodes 1, 8, and 7 are all BSTs of size 1.
  2. Node 5: Left (1) and right (8) are BSTs, and 1 < 5 < 8. So the subtree rooted at 5 is a BST of size 3.
  3. Node 15: Right child (7) is a BST, but 15 < 7 is not valid, so the subtree rooted at 15 is not a BST. Its largest BST is size 1 (either 15 or 7).
  4. Node 10: Left subtree (5) is a BST of size 3, right subtree (15) is not a BST. Since right is not a BST, the subtree rooted at 10 is not a BST.
  5. The largest BST subtree found is of size 3 (the subtree rooted at 5).

Time and Space Complexity

Brute-force Approach: For every node, check every possible subtree and validate if it's a BST, which could take O(N^2) time in the worst case (where N is the number of nodes).

Optimized Approach (this solution): Each node is visited exactly once, and all necessary information is computed and propagated in O(1) time per node. Therefore, the total time complexity is O(N).

The space complexity is O(H), where H is the height of the tree, due to the recursion stack. In the worst case (skewed tree), this is O(N); in a balanced tree, O(log N).

Summary

The key to solving the Largest BST Subtree problem efficiently is to use a post-order traversal, gathering information about each subtree as we return from recursion. By keeping track of whether subtrees are BSTs and their sizes, as well as the min and max values, we can determine in constant time per node whether a larger BST has been found. This approach is both elegant and efficient, reducing the time complexity from O(N^2) to O(N).