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 为树高。

浙公网安备 33010602011771号