题目描述

✅ 589. N 叉树的前序遍历

image-20260928224341408

image-20260928224341409

题意分析

前序遍历先访问当前节点,再按孩子列表从左到右的顺序,完整遍历每棵子树。输入中的 null 只是序列化时的分组标记,函数已经接收到节点及其孩子列表,不需要解析这些分隔符。

题目进阶要求迭代实现。可以用显式栈保存下一步要处理的子树,模拟递归的访问顺序。

解法:迭代栈

核心思路

[!blue]

栈中的每个节点代表一棵尚未开始遍历的子树,栈顶代表下一棵要处理的子树。开始时只压入根;每次弹出节点,立即记录它的值,完成前序中的“先访问根”。

接下来要从左到右处理它的孩子,但栈是后进先出,所以必须把孩子从右到左压入。最左孩子最后入栈,会最先被弹出;它的后代又被压在右侧兄弟之上,因此这棵子树会被完整处理后,才轮到下一个兄弟。这就与递归前序的顺序一致。

每次用当前节点的孩子替换待处理的整棵子树后,栈仍按正确先后顺序保存剩余任务。树中每个非根节点只有一个父节点,只会被父节点压入一次,因此既不重复访问,也不需要额外的访问集合。栈为空时,全部子树已经处理完毕。

解题步骤

  • 空树返回空结果,非空根入栈。
  • 弹出节点并记录值。
  • 逆序压入其孩子,循环直到栈空。

空树直接返回空结果。叶子没有孩子,记录值后不再压入节点;单节点树因而只执行一轮。孩子数量不固定,必须遍历完整列表。

代码实现

class Solution {
    public List<Integer> preorder(Node root) {
        List<Integer> res = new ArrayList<>();

        if (root == null) {
            return res;
        }

        Deque<Node> stack = new ArrayDeque<>();

        stack.push(root);

        while (!stack.isEmpty()) {
            Node cur = stack.pop();

            // 前序先访问当前子树的根
            res.add(cur.val);

            List<Node> children = cur.children;

            if (children == null) {
                continue;
            }

            // 逆序压栈,让最左子树先完整处理
            for (int i = children.size() - 1; i >= 0; i--) {
                stack.push(children.get(i));
            }
        }

        return res;
    }
}
func preorder(root *Node) []int {
    res := make([]int, 0)
    if root == nil {
        return res
    }

    stack := []*Node{
        root,
    }
    for len(stack) > 0 {
        cur := stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        // 前序先访问当前子树的根
        res = append(res, cur.Val)

        children := cur.Children
        // 逆序压栈,让最左子树先完整处理
        for i := len(children) - 1; i >= 0; i-- {
            stack = append(stack, children[i])
        }
    }

    return res
}

复杂度分析

  • 时间复杂度:$O(n)$,每个节点入栈、出栈各一次。
  • 空间复杂度:$O(n)$,不计返回结果。显式栈保存的是待处理子树,并非单条递归路径;根含大量孩子时栈就能达到线性规模,单链时则只保留一个节点。

关键点总结

[!green]

  • 弹出时访问根,逆序压入孩子,两个顺序共同实现前序。
  • 新发现的后代位于已有兄弟之上,保证先完整处理当前子树。

易错点总结

[!yellow]

  • 正序压入孩子,会先访问最右孩子。
  • 使用队列,会改变子树优先的处理顺序。
  • 压入孩子时就记录值,会把压栈顺序误当作前序顺序。

相似题目

题目 难度 关联与区别
144. 二叉树的前序遍历 简单 前序先访问根再访问所有孩子,本题把二叉树固定左右孩子扩展成孩子列表。
590. N 叉树的后序遍历 简单 遍历结构相同,原题要在所有孩子处理完后才输出根。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2024/42301082
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!