Leetcode 二叉树 4

题号直达: 98 验证二叉搜索树 | 230 二叉搜索树中第 K 小的元素

LeetCode 98. 验证二叉搜索树 ↑

题目

给定一个二叉树的根节点 root,判断其是否是一个有效的二叉搜索树(BST)。

有效 BST 的定义如下:

  • 若某结点有左子树,则左子树上所有结点的键值都严格小于该结点的键值;
  • 若某结点有右子树,则右子树上所有结点的键值都严格大于该结点的键值;
  • 左、右子树也必须是二叉搜索树。

提示

  • 树中节点数目在范围 [1, 10^4] 内
  • -2^31 ≤ Node.val ≤ 2^31 - 1

代码(Python)解法一:递归 + 左右子树极值与根比较

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

class Solution(object):
    def isValidBST(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: bool
        """
        if not root:
            return True

        left = self.isValidBST(root.left)
        right = self.isValidBST(root.right)
        if not left:
            return False
        if not right:
            return False

        if root.left:
            if root.left.val >= root.val:
                return False
            p = root.left
            while p.right:
                if p.right.val >= root.val:
                    return False
                p = p.right

        if root.right:
            if root.right.val <= root.val:
                return False
            p = root.right
            while p.left:
                if p.left.val <= root.val:
                    return False
                p = p.left

        return True

思路解析(解法一)

  • 先递归子树:isValidBST(root.left)、isValidBST(root.right) 分别保证左右子树在各自结构下满足 BST 的局部性质。
  • 与根的全局关系:仅看直接孩子不够,需要保证左子树中最大值仍小于根、右子树中最小值仍大于根。在 BST 中,左子树的最大值在从 root.left 一直往右走的终点;右子树的最小值在从 root.right 一直往左走的终点。题解中通过 while p.right / while p.left 沿脊检查路径上是否出现违反与 root.val 严格大小关系的结点。
  • 空树:root 为 None 时视为合法,返回 True。
  • 说明:中序遍历判单调递增见下方解法二;另有递归传递上下界等写法。本解法通过「递归 + 左右极值与根比较」将全局约束落在当前根上。

代码(Python)解法二:中序遍历(迭代 + 栈)

BST 的中序遍历为严格递增序列;用栈模拟中序,维护上一个访问结点值 temp,若当前 root.val <= temp 则非 BST。

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

class Solution(object):
    def isValidBST(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: bool
        """
        stack = []
        temp = -100000000000
        while root is not None or stack:
            while root is not None:
                stack.append(root)
                root = root.left
            root = stack.pop()
            if root.val <= temp:
                return False
            temp = root.val
            root = root.right
        return True

思路解析(解法二)

  • 性质:合法 BST 的中序遍历(左 → 根 → 右)得到的关键字序列严格递增。
  • 迭代 + 栈:外层循环等价于标准「一路向左入栈、弹栈访问、再转右」的中序模板;每弹出一个结点,其值应大于上一个已访问值 temp,否则说明存在「逆序」,不是 BST。
  • 前驱初值:temp 取小于题目值域下界的常数(如 -10^11,小于 -2^31),保证第一个结点必能通过 > 比较;也可写为 temp = float('-inf')。
  • 复杂度:每个结点入栈、出栈各一次,时间 O(n),n 为结点数;栈最坏 O(n)(链状树)。

LeetCode 230. 二叉搜索树中第 K 小的元素 ↑

题目

给定一个二叉搜索树的根节点 root 和一个整数 k,请你设计一个算法查找其中第 k 小的元素(k 从 1 开始计数)。

提示

  • 二叉树结点个数为 n,且 1 ≤ k ≤ n ≤ 10^4
  • 0 ≤ Node.val ≤ 10^4

进阶:如果二叉搜索树经常被修改(插入/删除操作),并且你需要频繁地查找第 k 小的值,你将如何优化算法?(例如:结点维护子树大小、平衡树 / 顺序统计树等思路。)

代码(Python)解法一:中序遍历 + 排序

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

class Solution(object):
    def kthSmallest(self, root, k):
        """
        :type root: Optional[TreeNode]
        :type k: int
        :rtype: int
        """
        res = []
        stack = []
        while root is not None or stack:
            while root is not None:
                stack.append(root)
                root = root.left
            root = stack.pop()
            res.append(root.val)
            root = root.right
        res.sort()
        return res[k - 1]

思路解析(解法一)

  • BST 与中序:二叉搜索树的中序遍历(左 → 根 → 右)得到升序关键字序列,第 k 小即该序列中第 k 个元素,对应下标 k - 1。
  • 实现:用栈迭代完成中序,将所有结点值依次写入 res,再取 res[k - 1]。
  • 关于 res.sort():对 BST 而言,中序结果已经全局有序,此处排序是冗余的;去掉 sort 后答案不变,且可省去 O(n log n) 的排序开销。
  • 可优化:若只需求第 k 小,可在中序过程中计数,访问到第 k 个结点时提前返回,时间 O(k)、空间栈深 O(h),无需存满表。
  • 进阶:频繁插入/删除时,可在结点增加子树结点数、或采用支持顺序统计的平衡结构,使查询第 k 小均摊更优。

代码(Python)解法二:子树结点数 + 类比分治(不推荐作本题默认写法)

思路:先做一次 DFS,用哈希表记录每个结点为根的子树结点数;查询第 k 小时,比较左子树结点数 left 与 k:若 left == k - 1,当前根即为第 k 小;若 left < k - 1,第 k 小在右子树,且 k 需减去左子树与根;否则在左子树中继续找。

说明:本题单次查询、静态树,中序遍历提前返回更简单;下面写法适合理解「进阶」中子树规模思想,但额外 O(n) 空间、建树时依赖结点对象作哈希键,且树被修改后需重建统计,故标注为不推荐作为首选模板。

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

class MyBst(object):
    def __init__(self, root):
        self.root = root
        self._node_num = {}
        self._count_node_num(root)

    def kth_smallest(self, k):
        node = self.root
        while node:
            left = self._get_node_num(node.left)
            if left < k - 1:
                node = node.right
                k -= left + 1
            elif left == k - 1:
                return node.val
            else:
                node = node.left

    def _count_node_num(self, node):
        if not node:
            return 0
        self._node_num[node] = 1 + self._count_node_num(node.left) + self._count_node_num(node.right)
        return self._node_num[node]

    def _get_node_num(self, node):
        return self._node_num[node] if node is not None else 0


class Solution(object):
    def kthSmallest(self, root, k):
        """
        :type root: Optional[TreeNode]
        :type k: int
        :rtype: int
        """
        return MyBst(root).kth_smallest(k)

思路解析(解法二)

  • 与进阶题的关系:对应「结点维护子树大小」:_count_node_num 在 O(n) 内填好 _node_num,之后每次比较左子树规模 left 与 k 的关系,决定走向左或右,等价于在有序结构上做类二分的下标跳转。
  • 分支含义:left == k - 1 表示比当前根小的结点恰有 k - 1 个,根即为第 k 小;left < k - 1 则第 k 小在右子树,且右子树中的排名变为 k - (left + 1),即代码里 k -= left + 1。
  • 为何标注不推荐:相对解法一的中序「提前停止」,本题多了一次全树遍历与 O(n) 哈希表;若树动态增删,需维护或重算 _node_num,工程上更常见的是在结点内嵌 size 字段或使用平衡树等结构。
  • 复杂度:预处理 O(n) 时间与 O(n) 额外空间;查询沿树高下降 O(h),h 为树高。
posted @ 2026-10-12 00:01  Phier  阅读(3)  评论(0)    收藏  举报