你写的二叉树递归全是错的——4道题踩坑实录(上)

文章目录

你写的二叉树递归全是错的------4道题踩坑实录(上)

中序遍历、对称判断、直径计算、层序遍历,看起来都会写,但细节全是坑。


一、中序遍历:别让临时列表拖慢你

最常见的写法,每层递归都新建一个 tmp 列表再逐个复制:

java 复制代码
// ❌ 慢写法
List<Integer> tmp = inorderTraversal(root.left);
for (Integer i : tmp) {
    ans.add(i);
}

n 个节点,n 个临时对象,n 次拷贝。时间复杂度都是 O(n),但常数项大很多。

正确做法:传同一个 List,直接 add

java 复制代码
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        dfs(root, ans);
        return ans;
    }

    private void dfs(TreeNode node, List<Integer> ans) {
        if (node == null) return;
        dfs(node.left, ans);
        ans.add(node.val);
        dfs(node.right, ans);
    }
}

只创建一个 List,所有节点直接往里加,没有中间拷贝。

记住:能传引用就传引用,别在递归里反复 new 对象。


二、对称二叉树:中序回文行不通

直觉上觉得:对称树的中序遍历是回文 ------错的。

构造反例:

复制代码
        1
       / \
      3   2
     /   / \
    2   3   1
   /
  1

中序遍历加哨兵后确实是回文,但这棵树明显不对称(左子树根=3,右子树根=2)。

根本原因:不同结构的树可以产生相同的中序序列。

还有第二个坑------用 != 比较 Integer 对象:

java 复制代码
if (list.get(l) != list.get(r))  // ❌ 比较引用,不是值

Integer 缓存范围只有 -128~127,超出就翻车。

正确做法:直接递归判镜像

java 复制代码
class Solution {
    public boolean isSymmetric(TreeNode root) {
        return isMirror(root.left, root.right);
    }

    private boolean isMirror(TreeNode l, TreeNode r) {
        if (l == null && r == null) return true;
        if (l == null || r == null || l.val != r.val) return false;
        return isMirror(l.left, r.right)
            && isMirror(l.right, r.left);
    }
}

核心只有一行:外侧对外侧,内侧对内侧

比较方式 结论
中序回文 ❌ 有反例,不可靠
递归判镜像 ✅ 直接、准确
!= 比较 Integer ❌ 比较引用
== 比较 int(.val ✅ 基本类型直接用 ==

三、二叉树直径:一个函数无法同时返回两个值

直径的定义:任意两节点之间路径的最大边数

很多人的思路是把 diameterOfBinaryTree 直接递归,返回值当深度用:

java 复制代码
// ❌ 错误
int left = diameterOfBinaryTree(root.left) + 1;
int right = diameterOfBinaryTree(root.right) + 1;
ans = Math.max(left + right, ans);
return Math.max(left, right);

问题:diameterOfBinaryTree 返回的是直径,不是深度。拿直径加 1 当深度,完全是两码事。

复制代码
    1
   / \
  2   3
 / \
4   5

左子树深度 = 2,直径 = 2
代码算出 left = diameter(节点2) + 1 = 3   ❌

根本矛盾:一个函数只能有一个返回值,但这里需要向上传递"深度",同时又要记录"直径"。

解法:用全局变量侧录直径,函数专心返回深度

java 复制代码
class Solution {
    int ans = 0;

    public int diameterOfBinaryTree(TreeNode root) {
        depth(root);
        return ans;
    }

    private int depth(TreeNode node) {
        if (node == null) return 0;
        int left  = depth(node.left);
        int right = depth(node.right);
        ans = Math.max(ans, left + right);   // 侧录直径
        return Math.max(left, right) + 1;    // 返回深度
    }
}
职责 实现方式
记录最大直径 ans 全局变量,每个节点更新一次
向父节点传递深度 return Math.max(left, right) + 1

直径 = 左深度 + 右深度;深度 = max(左深度, 右深度) + 1。两件事,必须分开。


四、层序遍历:锁定当前层大小是关键

层序遍历的难点在于:怎么区分哪些节点属于同一层?

常见错误思路:用额外的 tmp 列表存下一层,再倒回队列:

java 复制代码
// ❌ 绕了一圈
List<TreeNode> tmp = new ArrayList<>();
while (!que.isEmpty()) {
    TreeNode node = que.pollFirst();
    if (node.left != null) tmp.add(node.left);
    if (node.right != null) tmp.add(node.right);
}
for (TreeNode i : tmp) {
    que.offerLast(i);  // 又加回去
    ansTmp.add(i.val);
}

多了一次循环,还有构造器传 int 的编译错误:

java 复制代码
ans.add(new ArrayList<>(root.val));  // ❌ ArrayList 构造器接受 Collection,不接受 int

正确做法:进入每层前先记录队列大小

java 复制代码
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        if (root == null) return ans;
        Deque<TreeNode> que = new ArrayDeque<>();
        que.offerLast(root);
        while (!que.isEmpty()) {
            int size = que.size();          // 锁定当前层节点数
            List<Integer> level = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                TreeNode node = que.pollFirst();
                level.add(node.val);
                if (node.left  != null) que.offerLast(node.left);
                if (node.right != null) que.offerLast(node.right);
            }
            ans.add(level);
        }
        return ans;
    }
}

int size = que.size() 是核心------入队子节点之前先锁定这一层的数量,for 循环精确处理完这一层再进下一层。


总结

题目 核心坑 正确思路
中序遍历 递归内反复 new 列表 传引用,直接 add
对称二叉树 中序回文≠对称;!= 比较对象 递归判镜像;比较 .val
二叉树直径 返回值既当直径又当深度 全局变量侧录,helper 只返回深度
层序遍历 不知道当前层边界 int size = que.size() 锁定层大小

一句话记住:

  • 对象比较用 equals() 或直接比 .val(int)
  • 递归函数返回值只能有一个语义
  • BFS 分层靠的是进队前的 size 快照

觉得有帮助的话点个赞收藏,下篇继续二叉树进阶题。