1. 平衡二叉树(110题)深度解析
1.1 问题定义与核心思路
平衡二叉树是指任意节点的左右子树高度差不超过1的二叉树。判断一棵树是否为平衡二叉树,关键在于准确计算每个节点的左右子树高度差。这里采用后序遍历(左右中)的递归方法,原因有三:
- 需要先知道左右子树的高度才能计算当前节点的高度差
- 后序遍历天然符合"从下往上"计算高度的需求
- 可以在计算高度的同时完成平衡性判断,避免重复计算
1.2 代码实现详解
python复制class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
return self.get_height(root) != -1
def get_height(self, node):
if not node:
return 0
left = self.get_height(node.left)
right = self.get_height(node.right)
if left == -1 or right == -1 or abs(left - right) > 1:
return -1
return max(left, right) + 1
关键点解析:
get_height函数不仅计算高度,还负责平衡性判断- 使用-1作为不平衡的标志,避免额外变量
- 递归终止条件:空节点高度为0
- 每次递归返回当前子树的最大高度+1
1.3 时间复杂度与优化
最优时间复杂度O(n):每个节点只访问一次。相比暴力解法(对每个节点分别计算左右子树高度)的O(n^2)有显著提升。
注意:在实际面试中,面试官可能会追问为什么选择后序遍历而非前序。可以这样回答:"前序遍历是自上而下计算深度,而后序遍历是自下而上计算高度。判断平衡需要知道子树的高度信息,所以后序更合适。"
2. 二叉树的所有路径(257题)实战指南
2.1 回溯算法设计思路
收集所有根到叶子的路径,本质上是树的深度优先遍历(DFS)问题。需要解决两个关键点:
- 路径记录:需要在遍历时维护当前路径
- 回溯处理:离开节点时需要撤销选择
算法框架:
- 参数设计:当前节点、路径列表、结果集
- 终止条件:到达叶子节点时保存路径
- 递归逻辑:左右子树递归后需要回溯
2.2 代码实现与技巧
python复制class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
path = []
ans = []
if not root:
return []
self.travel(root, path, ans)
return ans
def travel(self, node, path, result):
path.append(node.val)
if not node.left and not node.right:
spath = "->".join(map(str, path))
result.append(spath)
return
if node.left:
self.travel(node.left, path, result)
path.pop() # 回溯
if node.right:
self.travel(node.right, path, result)
path.pop() # 回溯
关键细节:
path列表会随着递归层级变化- 使用
map(str, path)处理数字节点值 - 回溯操作必须对称(递归调用后立即pop)
2.3 常见错误与调试技巧
- 忘记回溯:会导致路径包含多余节点
- 路径拼接错误:非字符串节点需要转换
- 终止条件不完整:可能漏掉单边子树的情况
调试建议:
- 打印递归过程中的path变化
- 对空树和单节点树进行边界测试
- 使用可视化工具观察递归栈
3. 左叶子之和(404题)精讲
3.1 左叶子的精确定义
左叶子必须同时满足:
- 是父节点的左子节点
- 自身是叶子节点(无左右子树)
常见误区:
- 误判根节点
- 忽略右子树中的左叶子
- 将非叶子节点计入结果
3.2 递归解法详解
python复制class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
if not root.left and not root.right:
return 0
left_val = self.sumOfLeftLeaves(root.left)
right_val = self.sumOfLeftLeaves(root.right)
if root.left and not root.left.left and not root.left.right:
left_val = root.left.val
return left_val + right_val
递归三要素:
- 终止条件:空节点或叶子节点返回0
- 递归逻辑:分别计算左右子树的左叶子和
- 合并结果:当前节点的左叶子值(如果存在)加上子树结果
3.3 迭代解法对比
使用栈的迭代实现:
python复制def sumOfLeftLeaves(root):
if not root:
return 0
stack = [(root, False)]
total = 0
while stack:
node, is_left = stack.pop()
if not node.left and not node.right and is_left:
total += node.val
if node.right:
stack.append((node.right, False))
if node.left:
stack.append((node.left, True))
return total
比较分析:
- 递归更简洁但可能有栈溢出风险
- 迭代更易控制流程但代码稍复杂
- 时间复杂度都是O(n),空间复杂度取决于树高
4. 完全二叉树节点计数(222题)最优解
4.1 完全二叉树特性利用
完全二叉树定义:
- 除最后一层外,其他层节点全满
- 最后一层节点靠左排列
关键性质:
- 左子树高度 ≥ 右子树高度
- 满二叉树的节点数=2^h - 1
4.2 高效算法实现
python复制class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
left = root.left
right = root.right
count = 0
while left and right:
count += 1
left = left.left
right = right.right
if not left and not right:
return (2 << count) - 1
return 1 + self.countNodes(root.left) + self.countNodes(root.right)
算法步骤:
- 检查当前子树是否为满二叉树
- 是则直接计算节点数返回
- 否则递归计算左右子树
- 时间复杂度优化到O(logn * logn)
4.3 复杂度分析与证明
最坏情况分析:
- 每次递归都可能需要计算树高(O(logn))
- 递归深度最多O(logn)
- 因此总复杂度O(logn * logn)
数学归纳:
- 对于高度h的完全二叉树,递归方程为T(n)=T(n/2)+O(h)
- 由于h=logn,得到T(n)=O(log²n)
5. 二叉树问题通用解题框架
5.1 递归三要素模板
- 终止条件:通常对应空节点或叶子节点
- 递归调用:处理左右子树
- 结果合并:根据左右子树结果计算当前结果
伪代码示例:
python复制def solve(root):
if not root: # 终止条件
return base_case
left = solve(root.left) # 递归左子树
right = solve(root.right) # 递归右子树
return combine(root.val, left, right) # 合并结果
5.2 四种遍历方式选择指南
- 前序(中左右):适合自上而下的计算
- 中序(左中右):BST相关操作
- 后序(左右中):需要子树信息的计算
- 层序:按层次处理节点
5.3 调试与验证技巧
- 小规模测试用例验证
- 可视化递归过程(打印缩进)
- 边界条件检查:
- 空树
- 单节点树
- 左/右斜树
- 满二叉树
我在实际刷题中发现,二叉树问题的难点往往在于递归逻辑的设计和边界条件的处理。建议从简单的树结构开始,逐步增加复杂度,同时养成写注释的习惯,明确每个递归步骤的职责。对于回溯类问题,要特别注意状态的回滚时机,可以使用纸笔模拟递归栈的变化。