代码问题

  1. 不要忘记右子树

困惑

  1. 二叉树什么时候用递归,什么时候用层序遍历?
    二叉树选择递归(通常对应深度优先搜索DFS,包括前/中/后序)还是层序遍历(对应广度优先搜索BFS),核心取决于问题的需求场景树的结构特性——两者的本质差异是“遍历顺序”和“信息获取方式”,需结合具体问题判断。
一、优先用「递归(DFS)」的场景

递归的核心是“深入某一分支直到终点,再回溯处理其他分支”,适合需要“从根到叶子/从叶子到根”传递信息、或依赖子树状态推导当前节点状态的问题。其优势是代码简洁(贴合二叉树“子树与原树结构一致”的递归特性),无需手动维护栈/队列。

典型场景1:需要“深度优先”的路径/状态传递

当问题需要追踪“从根到叶子的路径”“子树的属性(如是否对称、是否为BST)”时,递归能自然地通过函数参数/返回值传递信息,避免手动管理路径/状态。
例子

  • 二叉树的所有路径(如之前的binaryTreePaths):递归时可通过参数传递当前路径,到达叶子时直接拼接结果;
  • 验证二叉搜索树(BST):需判断左子树所有节点 < 当前节点 < 右子树所有节点,递归可传递“允许的最大值/最小值”,逐层校验;
  • 二叉树的最大深度:递归时返回“左子树深度+1”或“右子树深度+1”,天然符合深度的定义。
典型场景2:树的深度较浅,无栈溢出风险

Python默认递归深度约1000,若树是“平衡二叉树”(深度≈log₂n,n为节点数),递归完全安全;即使是“斜树”(如所有节点只有左子树),只要深度≤1000,递归仍可正常运行。
反例:若树是深度为10000的斜树,递归会触发RecursionError,此时需改用迭代DFS或层序遍历。

典型场景3:代码简洁性优先

递归代码通常比迭代实现短50%以上,逻辑更直观。例如“二叉树的后序遍历”,递归只需3行核心代码,而迭代实现需手动维护栈标记“是否已访问子节点”,代码复杂得多。

二、优先用「层序遍历(BFS)」的场景

层序遍历的核心是“逐层处理所有节点,先处理当前层再处理下一层”,适合需要“分层信息”“最短路径”或“避免递归栈溢出”的问题。其优势是迭代实现稳定(无递归深度限制),且能直接获取每层的节点集合。

典型场景1:需要“分层信息”的问题

当问题明确要求“按层输出节点”“计算每层的最大值/平均值”“找到某一层的特定节点”时,层序遍历是唯一直接的解法——递归需要额外维护“当前层数”,逻辑繁琐。
例子

  • 二叉树的层序遍历(如LeetCode 102):要求返回[[第1层节点], [第2层节点], ...],层序遍历通过队列的“当前层长度”可轻松分组;
  • 二叉树每层的最大值(LeetCode 515):遍历每层时直接比较最大值,无需跨层传递信息;
  • 完全二叉树的节点个数(LeetCode 222):层序遍历到第一个“缺左/右子节点”的节点时,可提前终止,比递归更高效。
典型场景2:树的深度极深(避免递归栈溢出)

对于深度远超1000的“病态树”(如10^5个节点的斜树),递归会直接栈溢出,而层序遍历用队列实现,队列大小仅取决于“当前层的最大节点数”(平衡树中最大层节点数≈n/2,完全可控),无溢出风险。

典型场景3:需要“最短路径”或“尽早找到目标”

在二叉树中,“从根到叶子的最短路径”“找到距离根最近的满足条件的节点”等问题,层序遍历的“逐层处理”特性能确保“第一次遇到目标时,路径就是最短的”,无需遍历所有节点。
例子

  • 二叉树的最小深度(LeetCode 111):层序遍历到第一个叶子节点时,当前层数就是最小深度,可直接返回;若用递归,需遍历所有叶子节点后比较深度,效率更低。
三、总结:选择的核心依据
选择维度 优先用递归(DFS) 优先用层序遍历(BFS)
核心需求 深度优先(路径、子树状态) 分层信息(按层输出、层内统计)
树的结构 深度较浅(≤1000)、平衡树 深度极深(>1000)、病态树
性能/效率需求 无需分层信息时,递归效率与迭代相当 需最短路径、提前终止时,BFS更高效
代码复杂度 简洁(贴合二叉树递归特性) 稍复杂(需手动维护队列)
四、经典问题对应表
问题名称 推荐方式 原因分析
二叉树的所有路径 递归/迭代DFS 需要深度优先追踪路径,递归更简洁
验证二叉搜索树 递归 需传递“允许的最大/最小值”,递归参数更直观
二叉树的层序遍历 层序遍历 明确要求分层输出,层序遍历直接实现
二叉树每层的最大值 层序遍历 需按层统计最大值,层序遍历天然分组
二叉树的最小深度 层序遍历 找到第一个叶子节点即终止,效率更高
深度极深的二叉树遍历 层序遍历/迭代DFS 避免递归栈溢出

简单来说:“要深度,用递归;要分层,用层序”——根据问题的核心需求,结合树的结构特性,就能快速做出选择。

PLUS