Article / 文章

LeetCode 第333题:最大BST子树

给定一个二叉树,找到其中最大的二叉搜索树(BST)子树,并返回该子树的大小。其中,最大指的是子树节点数最多的。 二叉搜索树(BST)的定义: - 节点的左子树只包含小于当前节点的数。 - 节点的右子树只包含大于当前节点的数。 - 所有左子树和右子树自身必须也是二叉搜索树。

📖 文章摘要

本文详细解析LeetCode第333题“最大BST子树”,这是一道中等难度的二叉树问题。文章提供了自底向上的递归解法,包含C#、Python、C++三种语言实现,配有详细的算法分析和性能对比。适合想要提升二叉树和BST操作能力的程序员。

核心知识点: 二叉搜索树、递归、树的遍历、自底向上
难度等级: 中等
推荐人群: 具有基础数据结构知识,想要提升二叉树算法能力的程序员

题目描述

给定一个二叉树,找到其中最大的二叉搜索树(BST)子树,并返回该子树的大小。其中,最大指的是子树节点数最多的。

二叉搜索树(BST)的定义:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例

示例 1:

输入:
     10
    /  \
   5    15
  / \    \
 1   8    7
输出:3
解释:最大的BST子树是:
   5
  / \
 1   8

示例 2:

输入:
     4
    / \
   2   7
  / \
 2   3
输出:2

提示

  • 给定的二叉树的大小在 [0, 10^4] 范围内。
  • 每个节点的值在 [-10^4, 10^4] 范围内。

解题思路

方法:自底向上递归

使用自底向上的递归方法,对每个节点判断其是否构成BST。

关键点:

  • 使用辅助类存储子树信息
  • 自底向上传递节点信息
  • 维护子树的最大最小值
  • 判断BST的条件

具体步骤:

  1. 定义辅助类存储子树信息
  2. 递归处理左右子树
  3. 判断当前节点是否可以构成BST
  4. 更新最大BST子树大小
  5. 返回子树信息

时间复杂度:O(n),其中n为节点数 空间复杂度:O(h),其中h为树的高度

图解思路

算法流程分析表

步骤 操作 状态 说明
初始化 创建辅助类 - 存储子树信息
递归左子树 获取信息 左子树状态 判断左子树是否BST
递归右子树 获取信息 右子树状态 判断右子树是否BST
合并结果 判断当前节点 更新最大值 确定是否构成更大BST

示例分析

     10
    /  \
   5    15
  / \    \
 1   8    7

处理节点5:
- 左子树:1 (BST,大小1)
- 右子树:8 (BST,大小1)
- 节点5:构成BST,大小3

处理节点15:
- 右子树:7 (BST,大小1)
- 不构成BST(7 < 15)

最终结果:3(节点5的子树)

代码实现

C# 实现

public class Solution {
    private class TreeInfo {
        public bool IsBST { get; set; }
        public int Size { get; set; }
        public int Min { get; set; }
        public int Max { get; set; }
        
        public TreeInfo(bool isBST, int size, int min, int max) {
            IsBST = isBST;
            Size = size;
            Min = min;
            Max = max;
        }
    }
    
    private int maxSize = 0;
    
    public int LargestBSTSubtree(TreeNode root) {
        if (root == null) return 0;
        LargestBSTSubtreeHelper(root);
        return maxSize;
    }
    
    private TreeInfo LargestBSTSubtreeHelper(TreeNode node) {
        if (node == null) {
            return new TreeInfo(true, 0, int.MaxValue, int.MinValue);
        }
        
        // 递归处理左右子树
        var left = LargestBSTSubtreeHelper(node.left);
        var right = LargestBSTSubtreeHelper(node.right);
        
        // 判断当前节点是否可以构成BST
        if (left.IsBST && right.IsBST && 
            node.val > left.Max && node.val < right.Min) {
            // 可以构成BST
            int size = left.Size + right.Size + 1;
            maxSize = Math.Max(maxSize, size);
            return new TreeInfo(
                true,
                size,
                Math.Min(node.val, left.Min),
                Math.Max(node.val, right.Max)
            );
        }
        
        // 不能构成BST
        return new TreeInfo(false, 0, 0, 0);
    }
}

Python 实现

class Solution:
    def largestBSTSubtree(self, root: TreeNode) -> int:
        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)
            
            # 判断当前节点是否可以构成BST
            if (left_is_bst and right_is_bst and 
                node.val > left_max and node.val < right_min):
                # 可以构成BST
                size = left_size + right_size + 1
                self.max_size = max(self.max_size, size)
                return True, size, min(node.val, left_min), max(node.val, right_max)
            
            # 不能构成BST
            return False, 0, 0, 0
        
        self.max_size = 0
        helper(root)
        return self.max_size

C++ 实现

class Solution {
    struct TreeInfo {
        bool isBST;
        int size;
        int min;
        int max;
        TreeInfo(bool b, int s, int mn, int mx) 
            : isBST(b), size(s), min(mn), max(mx) {}
    };
    
    int maxSize = 0;
    
public:
    int largestBSTSubtree(TreeNode* root) {
        if (!root) return 0;
        largestBSTSubtreeHelper(root);
        return maxSize;
    }
    
private:
    TreeInfo largestBSTSubtreeHelper(TreeNode* node) {
        if (!node) {
            return TreeInfo(true, 0, INT_MAX, INT_MIN);
        }
        
        // 递归处理左右子树
        auto left = largestBSTSubtreeHelper(node->left);
        auto right = largestBSTSubtreeHelper(node->right);
        
        // 判断当前节点是否可以构成BST
        if (left.isBST && right.isBST && 
            node->val > left.max && node->val < right.min) {
            // 可以构成BST
            int size = left.size + right.size + 1;
            maxSize = max(maxSize, size);
            return TreeInfo(
                true,
                size,
                min(node->val, left.min),
                max(node->val, right.max)
            );
        }
        
        // 不能构成BST
        return TreeInfo(false, 0, 0, 0);
    }
};

执行结果

C# 实现

  • 执行用时:92 ms
  • 内存消耗:38.2 MB

Python 实现

  • 执行用时:44 ms
  • 内存消耗:15.6 MB

C++ 实现

  • 执行用时:8 ms
  • 内存消耗:22.1 MB

性能对比

语言 执行用时 内存消耗 特点
C# 92 ms 38.2 MB 代码结构清晰
Python 44 ms 15.6 MB 实现最简洁
C++ 8 ms 22.1 MB 性能最优

代码亮点

  1. 🎯 使用辅助类封装信息
  2. 💡 自底向上的递归设计
  3. 🔍 完整的边界条件处理
  4. 🎨 清晰的代码结构

常见错误分析

  1. 🚫 忘记处理空节点情况
  2. 🚫 BST判断条件错误
  3. 🚫 最大最小值更新错误
  4. 🚫 子树大小计算错误

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
自底向上递归 O(n) O(h) 一次遍历完成 需要额外空间
分治法 O(n) O(h) 思路清晰 代码较复杂

相关题目


📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新至第333题。


💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!