坚持刷leetcode第15天(二叉树->层序遍历,字节面试原题)

144 阅读4分钟

哪吒教你用“乾坤圈”搞定二叉树的层次遍历!

大家好,我是哪吒!今天咱们不闹海,也不打妖怪,来聊聊二叉树的层次遍历。你可能要问了:“哪吒,你不是玩乾坤圈和混天绫的吗?怎么搞起算法来了?”嘿嘿,别急,听我慢慢道来。其实,算法和法宝一样,都是解决问题的利器。今天我就用我的“乾坤圈”带你搞定二叉树的层次遍历!


什么是层次遍历?

层次遍历,顾名思义,就是一层一层地遍历二叉树。比如下面这棵树:

复制

        1
       / \
      2   3
     / \   \
    4   5   6

层次遍历的结果就是:[[1], [2, 3], [4, 5, 6]]。是不是很简单?但问题来了,怎么用代码实现呢?


递归 vs 非递归

很多人一看到树,就想用递归。递归确实简单,但今天咱们不走寻常路,用非递归的方式来实现层次遍历。为什么?因为递归虽然优雅,但容易“爆栈”(栈溢出),尤其是当树很深的时候。非递归方式虽然写起来复杂一点,但更稳健,适合处理大规模数据。


非递归实现思路

非递归实现层次遍历的核心思想是:用队列来辅助。队列是什么?你可以把它想象成一个“乾坤圈”,先进先出,先来的先处理。具体步骤如下:

  1. 初始化:把根节点放进队列。

  2. 循环处理

    • 每次从队列中取出当前层的所有节点,把它们的值存到一个列表中。
    • 把这些节点的子节点(左孩子和右孩子)放进队列,准备下一轮处理。
  3. 结束条件:当队列为空时,说明所有节点都处理完了。

是不是很简单?接下来,咱们用代码来实现这个逻辑。


Java 代码实现

public List<List<Integer>> levelOrder(TreeNode root) {
    // 如果根节点为空,直接返回空列表
    if (root == null) {
        return new ArrayList<>();
    }

    // 结果列表,存储每一层的节点值
    List<List<Integer>> res = new ArrayList<>();

    // 队列,用于辅助层次遍历
    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root); // 把根节点放进队列

    // 开始遍历
    while (!queue.isEmpty()) {
        // 当前层的节点数量
        int n = queue.size();

        // 存储当前层的节点值
        List<Integer> list = new ArrayList<>(n);

        // 遍历当前层的所有节点
        for (int i = 0; i < n; i++) {
            TreeNode node = queue.poll(); // 取出队头节点
            list.add(node.val); // 把节点值加入列表

            // 把左孩子和右孩子放进队列
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }

        // 把当前层的节点值列表加入结果
        res.add(list);
    }

    return res;
}

C++ 代码实现

vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> res;

    // 如果根节点为空,直接返回空列表
    if (root == nullptr) {
        return res;
    }

    // 队列,用于辅助层次遍历
    queue<TreeNode*> queue;
    queue.push(root); // 把根节点放进队列

    // 开始遍历
    while (!queue.empty()) {
        // 当前层的节点数量
        int size = queue.size();

        // 存储当前层的节点值
        vector<int> vec;

        // 遍历当前层的所有节点
        for (int i = 0; i < size; i++) {
            auto node = queue.front(); // 取出队头节点
            queue.pop(); // 弹出队头节点
            vec.push_back(node->val); // 把节点值加入列表

            // 把左孩子和右孩子放进队列
            if (node->left != nullptr) {
                queue.push(node->left);
            }
            if (node->right != nullptr) {
                queue.push(node->right);
            }
        }

        // 把当前层的节点值列表加入结果
        res.push_back(vec);
    }

    return res;
}

代码解析

  1. 队列的作用:队列就像一个“乾坤圈”,帮助我们按层次顺序处理节点。每次处理一层,把下一层的节点放进队列。
  2. 时间复杂度:每个节点都会被访问一次,所以时间复杂度是 O(n),其中 n 是节点数量。
  3. 空间复杂度:队列的最大长度是二叉树最宽的那一层,所以空间复杂度是 O(m),其中 m 是二叉树的最大宽度。

为什么坚持非递归?

你可能要问了:“哪吒,递归不是更简单吗?为什么要用非递归?”其实,非递归方式虽然写起来复杂一点,但它有两个优点:

  1. 避免栈溢出:递归调用会占用栈空间,如果树很深,容易导致栈溢出。
  2. 更直观:非递归方式更贴近计算机的执行逻辑,适合处理大规模数据。

就像我哪吒,虽然用乾坤圈打妖怪很爽,但有时候也得用混天绫,灵活应对各种情况!


总结

今天咱们用“乾坤圈”(队列)搞定了二叉树的层次遍历。通过非递归的方式,我们不仅避免了栈溢出的风险,还更深入地理解了层次遍历的本质。希望这篇文章能让你对二叉树遍历有新的认识,也希望大家在算法的道路上坚持不懈,像我哪吒一样,勇往直前!

最后,如果你觉得这篇文章有用,别忘了点赞、分享哦!咱们下次再见,继续用“乾坤圈”搞定更多算法难题!