1. 二叉树深度优先搜索(DFS)基础概念
深度优先搜索(Depth-First Search)是遍历或搜索树结构最常用的算法之一。对于二叉树而言,DFS的核心思想是尽可能深地探索每一条分支,直到无法继续深入为止,然后回溯到上一个节点继续探索其他分支。
二叉树DFS通常有三种实现方式:
- 前序遍历(Pre-order):根节点 → 左子树 → 右子树
- 中序遍历(In-order):左子树 → 根节点 → 右子树
- 后序遍历(Post-order):左子树 → 右子树 → 根节点
递归实现DFS的代码模板如下:
java复制void dfs(TreeNode root) {
if (root == null) return;
// 前序遍历位置
dfs(root.left);
// 中序遍历位置
dfs(root.right);
// 后序遍历位置
}
在实际问题中,我们需要根据具体需求选择适当的遍历顺序。例如:
- 需要先处理父节点再处理子节点时用前序
- 需要按大小顺序处理二叉搜索树时用中序
- 需要先知道子节点结果才能处理父节点时用后序
2. 计算布尔二叉树的值(力扣2331)
2.1 问题分析与解法
这道题要求我们计算一个特殊布尔二叉树的最终值。树中的每个节点要么是叶子节点(值为0或1),要么是非叶子节点(值为2或3,分别代表OR和AND操作)。
递归解法思路:
- 如果当前节点是叶子节点,直接返回其布尔值(0为false,1为true)
- 否则递归计算左右子树的值
- 根据当前节点的运算符对左右子树结果进行OR或AND运算
java复制public boolean evaluateTree(TreeNode root) {
if (root.left == null && root.right == null) {
return root.val == 1;
}
boolean left = evaluateTree(root.left);
boolean right = evaluateTree(root.right);
return root.val == 2 ? left || right : left && right;
}
时间复杂度:O(n),需要访问每个节点一次
空间复杂度:O(h),递归栈深度取决于树的高度
2.2 注意事项
- 确保正确处理边界条件,特别是空树的情况(虽然题目保证输入有效)
- 注意Java中布尔运算的短路特性,但在这个递归实现中不影响结果
- 对于大型树,递归可能导致栈溢出,可考虑使用显式栈的迭代实现
3. 求根节点到叶节点数字之和(力扣129)
3.1 解题思路
这个问题要求我们将从根节点到每个叶子节点的路径上的数字组成一个数,然后求所有这些数的和。
关键点:
- 需要维护从根到当前节点的路径值
- 遇到叶子节点时将当前路径值加入总和
- 使用深度优先搜索遍历所有路径
java复制public int sumNumbers(TreeNode root) {
return dfs(root, 0);
}
private int dfs(TreeNode root, int preSum) {
if (root == null) return 0;
int currentSum = preSum * 10 + root.val;
if (root.left == null && root.right == null) {
return currentSum;
}
return dfs(root.left, currentSum) + dfs(root.right, currentSum);
}
3.2 优化与讨论
- 路径构建方式:使用整数计算比字符串拼接更高效
- 非递归实现:可以使用栈同时保存节点和当前路径值
- 空间优化:如果树非常不平衡,递归可能导致栈溢出,迭代实现更安全
4. 二叉树剪枝(力扣814)
4.1 问题解析
题目要求我们剪去二叉树中所有不包含1的子树。这是一个典型的后序遍历应用场景,因为我们需要先知道子树的结果才能决定是否剪枝。
算法步骤:
- 递归处理左右子树
- 如果当前节点是叶子节点且值为0,返回null(剪除)
- 否则返回当前节点
java复制public TreeNode pruneTree(TreeNode root) {
if (root == null) return null;
root.left = pruneTree(root.left);
root.right = pruneTree(root.right);
if (root.left == null && root.right == null && root.val == 0) {
return null;
}
return root;
}
4.2 实现细节
- 必须先处理子树再处理当前节点(后序)
- 注意Java对象引用特性,必须重新赋值left/right指针
- 对于大型树,递归实现可能栈溢出,可考虑Morris遍历等O(1)空间算法
5. 验证二叉搜索树(力扣98)
5.1 二叉搜索树定义
二叉搜索树(BST)定义为:
- 左子树所有节点值小于根节点值
- 右子树所有节点值大于根节点值
- 左右子树也必须是BST
常见误区:仅检查当前节点与左右子节点的关系是不够的,必须保证整个子树的范围正确。
5.2 中序遍历解法
利用BST中序遍历结果为有序序列的特性:
java复制private long prev = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) return true;
if (!isValidBST(root.left)) return false;
if (root.val <= prev) return false;
prev = root.val;
return isValidBST(root.right);
}
5.3 剪枝优化
在发现不符合条件时立即返回,避免不必要的计算:
java复制public boolean isValidBST(TreeNode root) {
return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
private boolean validate(TreeNode node, long min, long max) {
if (node == null) return true;
if (node.val <= min || node.val >= max) return false;
return validate(node.left, min, node.val)
&& validate(node.right, node.val, max);
}
6. 二叉搜索树中第K小的元素(力扣230)
6.1 中序遍历应用
BST的中序遍历结果是有序的,因此第k小的元素就是中序遍历的第k个元素。
迭代实现:
java复制public int kthSmallest(TreeNode root, int k) {
Deque<TreeNode> stack = new ArrayDeque<>();
while (true) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (--k == 0) return root.val;
root = root.right;
}
}
6.2 递归实现
java复制private int count = 0;
private int result = 0;
public int kthSmallest(TreeNode root, int k) {
count = k;
inorder(root);
return result;
}
private void inorder(TreeNode node) {
if (node == null || count <= 0) return;
inorder(node.left);
if (--count == 0) {
result = node.val;
return;
}
inorder(node.right);
}
7. 二叉树的所有路径(力扣257)
7.1 回溯算法实现
需要记录从根节点到每个叶子节点的路径,典型的DFS+回溯问题。
java复制public List<String> binaryTreePaths(TreeNode root) {
List<String> paths = new ArrayList<>();
backtrack(root, new StringBuilder(), paths);
return paths;
}
private void backtrack(TreeNode node, StringBuilder path, List<String> paths) {
if (node == null) return;
int len = path.length();
if (len != 0) path.append("->");
path.append(node.val);
if (node.left == null && node.right == null) {
paths.add(path.toString());
} else {
backtrack(node.left, path, paths);
backtrack(node.right, path, paths);
}
path.setLength(len); // 回溯
}
7.2 注意事项
- 使用StringBuilder比字符串拼接更高效
- 必须正确处理回溯,恢复path状态
- 注意处理空树和单节点树的特殊情况
8. DFS算法总结与优化技巧
8.1 递归与迭代选择
- 递归:代码简洁,但可能栈溢出
- 迭代:使用显式栈,更安全但代码复杂
8.2 常见优化手段
- 剪枝:提前终止不必要的递归分支
- 记忆化:存储已计算结果避免重复计算
- 尾递归优化:某些语言支持,减少栈空间使用
8.3 二叉树DFS模板扩展
带路径记录的DFS模板:
java复制void dfs(TreeNode node, List<Integer> path, List<List<Integer>> result) {
if (node == null) return;
path.add(node.val);
if (node.left == null && node.right == null) {
result.add(new ArrayList<>(path));
}
dfs(node.left, path, result);
dfs(node.right, path, result);
path.remove(path.size() - 1); // 回溯
}
在实际面试和竞赛中,掌握这些DFS的变体和优化技巧可以显著提高解题效率。建议通过大量练习来熟悉不同场景下的DFS应用。