1. 二叉搜索树基础回顾与解题思路
二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树结构,它具有以下关键性质:
- 对于任意节点,其左子树所有节点的值都小于该节点的值
- 对于任意节点,其右子树所有节点的值都大于该节点的值
- 左右子树也必须是二叉搜索树
这个看似简单的定义在实际应用中却蕴含着强大的威力。以LeetCode 669题为例,当我们需要修剪BST时,这个性质允许我们做出高效的决策:如果当前节点值小于下限L,那么它的整个左子树都可以被舍弃;如果大于上限R,则右子树可以被整体舍弃。
提示:BST的中序遍历结果是一个有序序列,这个特性在108题(有序数组转BST)和538题(BST转累加树)中都是解题的关键。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. LeetCode 669:修剪二叉搜索树
2.1 问题重述与边界分析
给定一个二叉搜索树,同时给定最小边界L和最大边界R,要求修剪树使得所有节点的值都在[L, R]范围内。修剪后的树仍然应该保持BST的性质。
边界情况需要考虑:
- 空树的处理
- 所有节点值都小于L或大于R的情况
- L和R相等的情况
- 树中包含L或R值的情况
2.2 递归解法详解
递归是解决树问题的自然思路。对于当前节点:
- 如果节点值为null,直接返回null
- 如果节点值 < L,则整个左子树可以舍弃,对右子树递归修剪
- 如果节点值 > R,则整个右子树可以舍弃,对左子树递归修剪
- 如果节点值在[L, R]内,则递归修剪左右子树
python复制def trimBST(root, L, R):
if not root:
return None
if root.val < L:
return trimBST(root.right, L, R)
if root.val > R:
return trimBST(root.left, L, R)
root.left = trimBST(root.left, L, R)
root.right = trimBST(root.right, L, R)
return root
2.3 迭代解法实现
虽然递归简洁,但了解迭代解法也很重要。迭代法的关键在于找到新的根节点,然后修剪其子树:
python复制def trimBST(root, L, R):
# 先找到新的根节点
while root and (root.val < L or root.val > R):
if root.val < L:
root = root.right
else:
root = root.left
# 修剪左子树
curr = root
while curr:
while curr.left and curr.left.val < L:
curr.left = curr.left.right
curr = curr.left
# 修剪右子树
curr = root
while curr:
while curr.right and curr.right.val > R:
curr.right = curr.right.left
curr = curr.right
return root
注意:在实际面试中,建议先给出递归解法,然后根据面试官要求再实现迭代版本。递归解法更直观,而迭代解法空间效率更高。
3. LeetCode 108:将有序数组转换为二叉搜索树
3.1 问题分析与转化思路
给定一个升序排列的有序数组,要求将其转换为高度平衡的二叉搜索树。高度平衡意味着每个节点的两个子树深度差不超过1。
关键观察:
- 数组已经有序,相当于BST的中序遍历结果
- 要构建平衡BST,应该选择中间元素作为根节点
- 这个过程天然适合递归实现
3.2 递归构建平衡BST
python复制def sortedArrayToBST(nums):
def helper(left, right):
if left > right:
return None
mid = (left + right) // 2
root = TreeNode(nums[mid])
root.left = helper(left, mid - 1)
root.right = helper(mid + 1, right)
return root
return helper(0, len(nums) - 1)
3.3 选择中间节点的变体
当数组长度为偶数时,中间节点有两个选择。LeetCode接受任意一种解,但实际应用中可能有不同需求:
python复制# 选择偏左的中间节点
mid = (left + right) // 2
# 选择偏右的中间节点
mid = (left + right + 1) // 2
3.4 迭代解法探索
虽然递归是更自然的解法,但了解迭代实现有助于深入理解过程:
python复制def sortedArrayToBST(nums):
if not nums:
return None
root = TreeNode(0) # 临时值
stack = [(0, len(nums)-1, root)]
while stack:
left, right, node = stack.pop()
mid = (left + right) // 2
node.val = nums[mid]
if left <= mid - 1:
node.left = TreeNode(0)
stack.append((left, mid-1, node.left))
if mid + 1 <= right:
node.right = TreeNode(0)
stack.append((mid+1, right, node.right))
return root
4. LeetCode 538:把二叉搜索树转换为累加树
4.1 问题理解与转换规则
累加树(Greater Tree)的转换规则是:使每个节点的新值等于原树中大于或等于该节点值的所有节点值之和。
关键点:
- 由于BST的性质,所有大于当前节点的值都在其右子树
- 应该按照"右-根-左"的顺序遍历(反向中序遍历)
- 需要维护一个累加变量
4.2 递归解法实现
python复制def convertBST(root):
total = 0
def helper(node):
nonlocal total
if not node:
return
helper(node.right)
total += node.val
node.val = total
helper(node.left)
helper(root)
return root
4.3 迭代解法与Morris遍历
为了优化空间复杂度,可以使用迭代方式或Morris遍历:
python复制def convertBST(root):
total = 0
stack = []
node = root
while stack or node:
while node:
stack.append(node)
node = node.right
node = stack.pop()
total += node.val
node.val = total
node = node.left
return root
Morris遍历版本(O(1)空间):
python复制def convertBST(root):
total = 0
current = root
while current:
if not current.right:
total += current.val
current.val = total
current = current.left
else:
# 找到当前节点在中序遍历下的前驱节点
predecessor = current.right
while predecessor.left and predecessor.left != current:
predecessor = predecessor.left
if not predecessor.left:
predecessor.left = current
current = current.right
else:
predecessor.left = None
total += current.val
current.val = total
current = current.left
return root
5. 三题联解与BST特性深入
5.1 BST遍历顺序的灵活运用
这三道题目展示了BST不同遍历顺序的应用:
- 669题:前序遍历(先处理根节点)
- 108题:中序遍历(数组即中序结果)
- 538题:反向中序遍历(右-根-左)
5.2 递归与迭代的转换技巧
树问题通常有递归和迭代两种解法:
- 递归更直观,但可能有栈溢出风险
- 迭代更高效,但代码通常更复杂
- Morris遍历可以做到O(1)空间,但会修改树结构
5.3 实际应用场景举例
- 数据库索引:BST结构常用于数据库索引,修剪操作类似于范围查询优化
- 自动补全:有序数组转BST可以用于构建字典树的前身
- 统计分析:累加树的概念可以应用于数据累积分布的计算
6. 常见错误与调试技巧
6.1 指针丢失问题
在修剪BST时,常见的错误是直接修改指针而丢失子树:
python复制# 错误示例
if root.val < L:
root = root.right # 这样修改不会传递到上层
正确做法是通过返回值传递修改:
python复制if root.val < L:
return trimBST(root.right, L, R)
6.2 边界条件处理
在有序数组转BST时,容易忽略空数组或单元素数组的情况:
python复制# 必须检查
if not nums:
return None
6.3 累加顺序错误
在538题中,如果采用普通的中序遍历(左-根-右),将无法正确累加:
python复制# 错误的中序累加
def helper(node):
if not node:
return
helper(node.left) # 错误:先处理了较小的节点
total += node.val
node.val = total
helper(node.right)
7. 性能优化与进阶思考
7.1 尾递归优化可能性
在某些语言中,尾递归可以被优化为迭代。例如669题的递归版本:
python复制def trimBST(root, L, R):
if not root:
return None
if root.val < L:
return trimBST(root.right, L, R) # 尾递归
if root.val > R:
return trimBST(root.left, L, R) # 尾递归
root.left = trimBST(root.left, L, R)
root.right = trimBST(root.right, L, R)
return root
7.2 并行化处理可能性
对于大型BST,可以考虑并行化处理子树。例如在修剪操作中,左右子树的修剪可以并行执行。
7.3 持久化数据结构实现
如果需要保留原始树结构,可以实现持久化BST,使修剪操作返回新树而不修改原树:
python复制def persistent_trimBST(root, L, R):
if not root:
return None
if root.val < L:
return persistent_trimBST(root.right, L, R)
if root.val > R:
return persistent_trimBST(root.left, L, R)
new_root = TreeNode(root.val)
new_root.left = persistent_trimBST(root.left, L, R)
new_root.right = persistent_trimBST(root.right, L, R)
return new_root
8. 相似题目扩展练习
为了巩固BST相关算法,推荐尝试以下LeetCode题目:
-
- 删除BST中的节点
-
- BST中的插入操作
-
- BST中第K小的元素
-
- 验证BST
-
- BST迭代器
每道题目都考察了BST的不同方面操作,建议按照以下顺序练习:
- 验证BST(理解性质)
- 查找第K小元素(遍历应用)
- 插入/删除节点(结构调整)
- BST迭代器(遍历控制)
在实际编写代码时,建议先画出示例树的图形表示,手动模拟操作过程,再转化为代码实现。这种"可视化思考"的方法能显著提高解题效率和正确率。
