文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 进阶
- 解法一
- 思路和算法
- 代码
- 复杂度分析
- 解法二
- 思路和算法
- 代码
- 复杂度分析
- 解法三
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:N 叉树的前序遍历
出处:589. N 叉树的前序遍历
难度
3 级
题目描述
要求
给定一个 N 叉树的根结点 root \texttt{root} root,返回其结点值的前序遍历。
N 叉树在输入中按层序遍历序列化表示,每组子结点由空值 null \texttt{null} null 分隔(请参见示例)。
示例
示例 1:
输入: root = [1,null,3,2,4,null,5,6] \texttt{root = [1,null,3,2,4,null,5,6]} root = [1,null,3,2,4,null,5,6]
输出: [1,3,5,6,2,4] \texttt{[1,3,5,6,2,4]} [1,3,5,6,2,4]
示例 2:
输入: root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14] \texttt{root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]} root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出: [1,2,3,6,7,11,14,4,8,12,5,9,13,10] \texttt{[1,2,3,6,7,11,14,4,8,12,5,9,13,10]} [1,2,3,6,7,11,14,4,8,12,5,9,13,10]
数据范围
- 树中结点数目在范围 [0, 10 4 ] \texttt{[0, 10}^\texttt{4}\texttt{]} [0, 104] 内
- 0 ≤ Node.val ≤ 10 4 \texttt{0} \le \texttt{Node.val} \le \texttt{10}^\texttt{4} 0≤Node.val≤104
- N 叉树的高度小于或等于 1000 \texttt{1000} 1000
进阶
递归解法很简单,你可以使用迭代解法完成吗?
解法一
思路和算法
N 叉树的前序遍历的方法为:首先遍历根结点,然后按照从左到右的顺序依次遍历每个子树,对于每个子树使用同样的方法遍历。由于遍历过程具有递归的性质,因此可以使用递归的方法实现 N 叉树的前序遍历。
递归的终止条件是当前结点为空。对于非终止条件,递归的做法如下。
-
将当前结点的结点值加入前序遍历序列。
-
按照从左到右的顺序,依次对当前结点的每个子树调用递归。
遍历结束之后即可得到前序遍历序列。
代码
class Solution {List<Integer> traversal = new ArrayList<Integer>();public List<Integer> preorder(Node root) {preorderVisit(root);return traversal;}public void preorderVisit(Node node) {if (node == null) {return;}traversal.add(node.val);List<Node> children = node.children;for (Node child : children) {preorderVisit(child);}}
}
复杂度分析
-
时间复杂度: O ( m ) O(m) O(m),其中 m m m 是 N 叉树的结点数。每个结点都被访问一次。
-
空间复杂度: O ( m ) O(m) O(m),其中 m m m 是 N 叉树的结点数。空间复杂度主要是递归调用的栈空间,取决于 N 叉树的高度,最坏情况下 N 叉树的高度是 O ( m ) O(m) O(m)。
解法二
思路和算法
使用迭代的方法实现 N 叉树的前序遍历,则需要使用栈存储结点。
由于前序遍历的过程中,对于同一个结点的子树的访问顺序是从左到右,因此当访问一个结点之后,将该结点的所有子结点按照从右到左的顺序依次入栈,则利用栈的后进先出的特点,子结点的出栈顺序为从左到右的顺序,和前序遍历的顺序相同。
当树为空时,前序遍历列表为空。当树不为空时,首先将根结点入栈,然后按照如下操作执行前序遍历。
-
将一个结点出栈,将当前结点的结点值加入前序遍历序列。
-
将当前结点的所有子结点按照从右到左的顺序依次入栈。
-
重复上述操作,直到栈为空时,前序遍历结束。
代码
class Solution {public List<Integer> preorder(Node root) {List<Integer> traversal = new ArrayList<Integer>();if (root == null) {return traversal;}Deque<Node> stack = new ArrayDeque<Node>();stack.push(root);while (!stack.isEmpty()) {Node node = stack.pop();traversal.add(node.val);List<Node> children = node.children;for (int i = children.size() - 1; i >= 0; i--) {stack.push(children.get(i));}}return traversal;}
}
复杂度分析
-
时间复杂度: O ( m ) O(m) O(m),其中 m m m 是 N 叉树的结点数。每个结点都被访问一次。
-
空间复杂度: O ( m ) O(m) O(m),其中 m m m 是 N 叉树的结点数。空间复杂度主要是栈空间,栈内元素个数不超过 m m m。
解法三
思路和算法
另一种使用迭代实现 N 叉树的前序遍历的方法是使用哈希表存储每个结点的已访问的最后一个子结点下标(以下简称「子结点下标」),而不是将子结点按照从右到左的顺序入栈,同样需要使用栈存储结点。
从根结点开始遍历,遍历的终止条件是栈为空且当前结点为空。遍历的做法如下。
-
如果当前结点不为空,则将当前结点的结点值加入前序遍历序列,将当前结点入栈。如果当前结点不是叶结点,则将当前结点的子结点下标设为 0 0 0,将当前结点移动到其子结点中的最左侧的子结点,重复上述操作。如果当前结点是叶结点,则在执行上述操作之后将当前结点设为空。
-
将栈顶结点的子结点下标加 1 1 1 记为新下标,则新下标为下一个待访问的子结点下标。如果新下标在子结点下标范围内则用新下标更新栈顶结点的子结点下标,将当前结点设为下一个待访问的结点;如果新下标不在子结点下标范围内则将栈顶结点出栈,将当前结点设为空。
-
重复上述操作,直到达到遍历的终止条件。
代码
class Solution {public List<Integer> preorder(Node root) {List<Integer> traversal = new ArrayList<Integer>();if (root == null) {return traversal;}Map<Node, Integer> map = new HashMap<Node, Integer>();Deque<Node> stack = new ArrayDeque<Node>();Node node = root;while (!stack.isEmpty() || node != null) {while (node != null) {traversal.add(node.val);stack.push(node);List<Node> children = node.children;if (!children.isEmpty()) {map.put(node, 0);node = children.get(0);} else {node = null;}}node = stack.peek();int index = map.getOrDefault(node, -1) + 1;List<Node> children = node.children;if (index < children.size()) {map.put(node, index);node = children.get(index);} else {stack.pop();map.remove(node);node = null;}}return traversal;}
}
复杂度分析
-
时间复杂度: O ( m ) O(m) O(m),其中 m m m 是 N 叉树的结点数。每个结点都被访问一次。
-
空间复杂度: O ( m ) O(m) O(m),其中 m m m 是 N 叉树的结点数。空间复杂度主要是哈希表和栈空间,哈希表和栈内元素个数都不超过 m m m。