1. 二叉树算法精讲:从平衡性检测到节点统计
作为一名有多年算法教学经验的工程师,我经常被问到如何系统性地掌握二叉树相关算法。今天我们就来深入剖析LeetCode上四道经典二叉树题目:平衡二叉树判断、所有路径收集、左叶子节点求和以及完全二叉树节点统计。这些题目看似独立,实则共同构建了我们对二叉树遍历、递归和回溯的完整认知体系。
2. 平衡二叉树检测:后序遍历的经典应用
2.1 问题定义与核心思路
平衡二叉树(AVL树)是计算机科学中最重要的数据结构之一,它的定义是:对于树中的每个节点,其左右子树的高度差绝对值不超过1。这个性质保证了树的查询效率始终维持在O(log n)级别。
判断一棵树是否平衡的关键在于计算每个节点的左右子树高度差。这里我们需要采用后序遍历(左右中)的顺序,因为必须先知道子树的高度才能计算当前节点的高度差。
2.2 递归实现细节
java复制class Solution {
public boolean isBalanced(TreeNode root) {
return getHeight(root) != -1;
}
private int getHeight(TreeNode node) {
if (node == null) return 0;
int leftHeight = getHeight(node.left);
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node.right);
if (rightHeight == -1) return -1;
if (Math.abs(leftHeight - rightHeight) > 1) {
return -1;
}
return 1 + Math.max(leftHeight, rightHeight);
}
}
这个实现有几个关键点需要注意:
- 使用-1作为不平衡的标志值
- 在递归过程中提前终止不平衡的情况
- 高度计算采用1 + max(left, right)的方式
2.3 时间复杂度分析
该算法的时间复杂度为O(n),其中n是树中节点的数量。这是因为每个节点只会被访问一次。空间复杂度取决于递归栈的深度,最坏情况下(树退化为链表)为O(n)。
3. 二叉树所有路径:前序遍历与回溯的完美结合
3.1 问题特点与解法选择
收集从根节点到所有叶子节点的路径,这明显是一个需要"从头到尾"记录路径的问题,因此前序遍历(中左右)是最自然的选择。同时,由于需要尝试不同的路径,回溯算法的思想在这里也非常适用。
3.2 递归与回溯的实现
java复制class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
if (root == null) return res;
backtrack(root, new ArrayList<>(), res);
return res;
}
private void backtrack(TreeNode node, List<Integer> path, List<String> res) {
path.add(node.val);
if (node.left == null && node.right == null) {
res.add(buildPathString(path));
return;
}
if (node.left != null) {
backtrack(node.left, path, res);
path.remove(path.size() - 1); // 回溯
}
if (node.right != null) {
backtrack(node.right, path, res);
path.remove(path.size() - 1); // 回溯
}
}
private String buildPathString(List<Integer> path) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < path.size() - 1; i++) {
sb.append(path.get(i)).append("->");
}
sb.append(path.get(path.size() - 1));
return sb.toString();
}
}
3.3 关键注意事项
- 回溯操作必须与递归调用成对出现
- 使用StringBuilder构建路径字符串效率更高
- 叶子节点判断条件是左右子节点均为null
提示:在实际面试中,面试官可能会要求你同时提供递归和迭代两种解法。迭代法可以使用栈来实现,保留访问路径。
4. 左叶子节点求和:后序遍历的特殊应用
4.1 左叶子的定义与识别
左叶子节点是指:某个节点的左孩子存在,且这个左孩子没有自己的左右孩子。注意是"左孩子"而不是"左子树"。这一点经常容易混淆。
识别左叶子的条件表达式:
java复制node.left != null && node.left.left == null && node.left.right == null
4.2 递归解法实现
java复制class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if (root == null) return 0;
int leftSum = sumOfLeftLeaves(root.left);
int rightSum = sumOfLeftLeaves(root.right);
int current = 0;
if (root.left != null &&
root.left.left == null &&
root.left.right == null) {
current = root.left.val;
}
return current + leftSum + rightSum;
}
}
4.3 迭代解法对比
虽然递归解法简洁,但了解迭代解法也很重要。使用栈或队列进行层次遍历,同时检查每个节点的左孩子是否为叶子节点:
java复制public int sumOfLeftLeaves(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int sum = 0;
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node.left != null) {
if (node.left.left == null && node.left.right == null) {
sum += node.left.val;
} else {
queue.offer(node.left);
}
}
if (node.right != null) {
queue.offer(node.right);
}
}
return sum;
}
5. 完全二叉树节点统计:结合完全二叉树特性的优化
5.1 完全二叉树的性质
完全二叉树是指除了最后一层外,其他层的节点都完全填满,且最后一层的节点都集中在左侧。这种结构有两个重要特性:
- 对于高度为h的完全二叉树,节点数在[2^h, 2^(h+1)-1]之间
- 左子树的高度总是大于等于右子树高度
5.2 普通递归法与优化递归法对比
普通递归法(适用于任何二叉树):
java复制public int countNodes(TreeNode root) {
if (root == null) return 0;
return 1 + countNodes(root.left) + countNodes(root.right);
}
优化后的完全二叉树专属解法:
java复制public int countNodes(TreeNode root) {
if (root == null) return 0;
int leftDepth = getDepth(root.left);
int rightDepth = getDepth(root.right);
if (leftDepth == rightDepth) {
// 左子树是满二叉树
return (1 << leftDepth) + countNodes(root.right);
} else {
// 右子树是满二叉树
return (1 << rightDepth) + countNodes(root.left);
}
}
private int getDepth(TreeNode node) {
int depth = 0;
while (node != null) {
depth++;
node = node.left;
}
return depth;
}
5.3 时间复杂度分析
优化后的算法时间复杂度为O(log n * log n)。这是因为:
- 每次递归调用都会将问题规模减半
- 每次计算深度需要O(log n)时间
- 总共有O(log n)次递归调用
相比之下,普通递归法的时间复杂度是O(n)。对于大型完全二叉树,优化算法的性能优势非常明显。
6. 二叉树算法实战经验总结
在实际工程和面试中处理二叉树问题时,我有以下几点深刻体会:
-
遍历顺序决定算法结构:
- 前序:适合需要"从头开始"构建信息的场景(如路径记录)
- 中序:适合BST相关操作
- 后序:适合需要子树信息的场景(如高度计算)
-
递归终止条件要严谨:
- 处理null节点的情况
- 明确叶子节点的判断标准
- 考虑单侧子树为空的情况
-
完全二叉树的特性可以大幅优化算法效率,但要注意:
- 正确识别满二叉树的情况
- 深度计算要一致(通常使用最左路径)
-
回溯算法的要点:
- 添加和移除操作要对称
- 结果收集要在正确的位置进行
- 使用可变数据结构(如List)时要小心并发修改
这些题目虽然来自LeetCode,但它们反映的算法思想在实际开发中随处可见。比如文件系统的目录遍历、组织架构的层级查询、决策树的分析等场景,都需要类似的树形结构处理技巧。