1. 二叉树与二叉搜索树核心算法全解析
作为数据结构中最基础的树形结构,二叉树和二叉搜索树在算法面试和工程实践中占据着重要地位。最近在准备算法面试时,我系统梳理了二叉树相关的十多个经典问题,发现很多题目看似独立,实则存在内在联系。今天我就把这些高频面试题的解题思路和实战技巧整理成文,希望能帮助大家少走弯路。
二叉树的特点是每个节点最多有两个子节点,而二叉搜索树(BST)在此基础上增加了"左子树所有节点值小于根节点,右子树所有节点值大于根节点"的约束条件。这种特性使得BST的查找、插入、删除等操作都能在O(log n)时间内完成。下面我将从基础操作到高级应用,逐步拆解这十个经典问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 二叉树基础操作
2.1 合并二叉树
合并两棵二叉树的思路很直观:同步遍历两棵树的对应节点,将它们的值相加作为新节点的值。如果某位置一棵树有节点而另一棵没有,则直接使用存在的节点。
python复制def mergeTrees(t1, t2):
if not t1:
return t2
if not t2:
return t1
t1.val += t2.val
t1.left = mergeTrees(t1.left, t2.left)
t1.right = mergeTrees(t1.right, t2.right)
return t1
注意:在实际工程中,我们通常不希望修改原始树结构,这时可以创建新节点而不是复用t1/t2的节点。
2.2 二叉树的最近公共祖先
寻找二叉树中两个节点的最近公共祖先(LCA)是经典问题。递归解法思路是:如果当前节点是p或q中的一个,或者p和q分别位于当前节点的左右子树中,则当前节点就是LCA。
python复制def lowestCommonAncestor(root, p, q):
if not root or root == p or root == q:
return root
left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
if left and right:
return root
return left if left else right
时间复杂度O(n),空间复杂度O(h),h为树高。
3. 二叉搜索树基础操作
3.1 二叉搜索树中的搜索
利用BST的性质可以高效搜索:目标值小于当前节点值则搜索左子树,大于则搜索右子树,等于则找到目标。
python复制def searchBST(root, val):
while root and root.val != val:
root = root.left if val < root.val else root.right
return root
迭代实现避免了递归栈开销,时间复杂度O(h),h为树高。
3.2 验证二叉搜索树
验证BST的关键是确保每个节点值都在正确范围内。常见错误是只检查节点与直接子节点的关系,实际上需要维护上下界。
python复制def isValidBST(root):
def helper(node, lower=float('-inf'), upper=float('inf')):
if not node:
return True
val = node.val
if val <= lower or val >= upper:
return False
return helper(node.left, lower, val) and helper(node.right, val, upper)
return helper(root)
中序遍历解法也很直观:BST的中序遍历结果应该是严格递增的。
3.3 二叉搜索树的最小绝对差
BST的中序遍历序列是递增的,因此最小绝对差一定出现在相邻节点之间。
python复制def getMinimumDifference(root):
prev, min_diff = None, float('inf')
def inorder(node):
nonlocal prev, min_diff
if not node:
return
inorder(node.left)
if prev:
min_diff = min(min_diff, node.val - prev.val)
prev = node
inorder(node.right)
inorder(root)
return min_diff
时间复杂度O(n),空间复杂度O(h)。
4. 二叉搜索树进阶操作
4.1 二叉搜索树中的众数
找出BST中出现频率最高的元素。利用BST中序遍历有序的特性,可以在遍历时统计当前值的出现次数。
python复制def findMode(root):
max_count = count = 0
prev = None
result = []
def inorder(node):
nonlocal prev, count, max_count, result
if not node:
return
inorder(node.left)
if node.val == prev:
count += 1
else:
count = 1
if count > max_count:
max_count = count
result = [node.val]
elif count == max_count:
result.append(node.val)
prev = node.val
inorder(node.right)
inorder(root)
return result
4.2 二叉搜索树的最近公共祖先
相比普通二叉树,BST的LCA可以利用节点值的大小关系简化搜索过程。
python复制def lowestCommonAncestor(root, p, q):
while root:
if p.val < root.val and q.val < root.val:
root = root.left
elif p.val > root.val and q.val > root.val:
root = root.right
else:
return root
return None
时间复杂度降至O(h),且无需递归栈空间。
5. 二叉搜索树的修改操作
5.1 二叉搜索树的插入操作
BST插入新节点需要保持BST性质。从根节点开始,根据值大小决定向左还是向右,直到找到空位。
python复制def insertIntoBST(root, val):
if not root:
return TreeNode(val)
if val < root.val:
root.left = insertIntoBST(root.left, val)
else:
root.right = insertIntoBST(root.right, val)
return root
迭代实现可以避免递归深度问题:
python复制def insertIntoBST(root, val):
node = root
while node:
if val < node.val:
if not node.left:
node.left = TreeNode(val)
return root
node = node.left
else:
if not node.right:
node.right = TreeNode(val)
return root
node = node.right
return TreeNode(val)
5.2 删除二叉搜索树中的节点
删除操作较为复杂,需要考虑三种情况:
- 节点是叶子节点:直接删除
- 节点有一个子节点:用子节点替代
- 节点有两个子节点:用右子树的最小值替代当前节点,然后删除那个最小值节点
python复制def deleteNode(root, key):
if not root:
return None
if key < root.val:
root.left = deleteNode(root.left, key)
elif key > root.val:
root.right = deleteNode(root.right, key)
else:
if not root.left:
return root.right
if not root.right:
return root.left
min_node = findMin(root.right)
root.val = min_node.val
root.right = deleteNode(root.right, min_node.val)
return root
def findMin(node):
while node.left:
node = node.left
return node
5.3 修剪二叉搜索树
修剪BST使所有节点值位于[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
6. 实战技巧与常见问题
6.1 递归与迭代的选择
递归代码简洁但可能有栈溢出风险;迭代效率高但代码复杂。对于BST操作:
- 搜索、插入:迭代更优
- 删除、修剪:递归更直观
- 遍历:递归更简洁,大数据量考虑迭代
6.2 边界条件处理
常见陷阱:
- 空树处理
- 删除根节点
- 插入重复值(根据需求决定是否允许)
- 浮点数比较时的精度问题
6.3 性能优化
对于频繁修改的BST:
- 维护平衡(AVL树、红黑树)
- 记录子树大小以支持排名查询
- 缓存常用查询结果
6.4 测试用例设计
完整测试应包含:
- 空树
- 单节点树
- 完全不平衡树(退化成链表)
- 满二叉树
- 随机生成的树
- 重复值测试(如允许)
7. 扩展应用
7.1 不同的二叉搜索树
计算由1...n能组成多少种不同的BST。这是典型的动态规划问题:
python复制def numTrees(n):
dp = [0] * (n + 1)
dp[0], dp[1] = 1, 1
for i in range(2, n + 1):
for j in range(1, i + 1):
dp[i] += dp[j - 1] * dp[i - j]
return dp[n]
7.2 最优二叉搜索树
对于给定访问频率的键集合,构造平均查找代价最小的BST。使用动态规划:
python复制def optimalBST(keys, freq):
n = len(keys)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = freq[i]
for L in range(2, n + 1):
for i in range(n - L + 1):
j = i + L - 1
dp[i][j] = float('inf')
for r in range(i, j + 1):
cost = sum(freq[i:j+1]) + (dp[i][r-1] if r > i else 0) + (dp[r+1][j] if r < j else 0)
if cost < dp[i][j]:
dp[i][j] = cost
return dp[0][n-1]
时间复杂度O(n^3),可用Knuth优化到O(n^2)。
在实际工程中,BST的这些算法思想可以应用于数据库索引、文件系统目录结构、路由表等场景。掌握这些基础算法不仅能帮助通过技术面试,更能培养解决复杂问题的系统化思维。
