LeetCode 589. N 叉树的前序遍历
问题描述
给定一个 N 叉树的根节点 root,返回其节点值的 前序遍历。
N 叉树在输入中按层序遍历进行序列化表示,每组子节点由 null 分隔(参见示例)。
示例:
输入: root = [1,null,3,2,4,null,5,6]
输出: [1,3,5,6,2,4]
输入: 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]
算法思路
前序遍历:
- 访问根节点
- 递归地前序遍历每个子树(从左到右)
方法:
- 递归:最直观的实现方式
- 迭代:使用栈模拟递归过程
- Morris遍历:不适用N叉树(Morris遍历主要针对二叉树)
代码实现
方法一:递归
import java.util.*;
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
/**
* 递归:N叉树前序遍历
*
* @param root N叉树根节点
* @return 前序遍历结果列表
*/
public List<Integer> preorder(Node root) {
List<Integer> result = new ArrayList<>();
preorderHelper(root, result);
return result;
}
/**
* 递归函数
*
* @param node 当前节点
* @param result 结果列表
*/
private void preorderHelper(Node node, List<Integer> result) {
// 空节点处理
if (node == null) {
return;
}
// 访问当前节点(根)
result.add(node.val);
// 递归遍历所有子节点(从左到右)
if (node.children != null) {
for (Node child : node.children) {
preorderHelper(child, result);
}
}
}
}
方法二:迭代
import java.util.*;
class Solution {
/**
* 迭代:使用栈实现N叉树前序遍历
*
* @param root N叉树根节点
* @return 前序遍历结果列表
*/
public List<Integer> preorder(Node root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<Node> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
// 弹出栈顶节点并访问
Node node = stack.pop();
result.add(node.val);
// 将子节点逆序压入栈中(确保从左到右遍历)
if (node.children != null) {
// 从右到左压栈,这样弹出时就是从左到右
for (int i = node.children.size() - 1; i >= 0; i--) {
stack.push(node.children.get(i));
}
}
}
return result;
}
}
方法三:优化迭代
import java.util.*;
class Solution {
/**
* 优化迭代:使用Deque作为栈,代码更清晰
*
* @param root N叉树根节点
* @return 前序遍历结果列表
*/
public List<Integer> preorder(Node root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Deque<Node> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
result.add(node.val);
// 逆序添加子节点
if (node.children != null) {
Collections.reverse(node.children);
for (Node child : node.children) {
stack.push(child);
}
// 恢复原顺序(如果需要保持原树不变)
Collections.reverse(node.children);
}
}
return result;
}
}
方法四:使用LinkedList
import java.util.*;
class Solution {
/**
* 优化添加顺序:直接按正确顺序处理子节点
*
* @param root N叉树根节点
* @return 前序遍历结果列表
*/
public List<Integer> preorder(Node root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<Node> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
result.add(node.val);
// 如果子节点存在,按逆序压栈
if (node.children != null && !node.children.isEmpty()) {
// 使用ListIterator从后往前遍历
ListIterator<Node> iterator = node.children.listIterator(node.children.size());
while (iterator.hasPrevious()) {
stack.push(iterator.previous());
}
}
}
return result;
}
}
算法分析
- 时间复杂度:O(n) - 每个节点访问一次
- 空间复杂度:
- 递归:O(h) - h是树的高度(递归栈深度)
- 迭代:O(h) - 栈空间(最坏情况下为O(n))
算法过程
输入:root = [1,null,3,2,4,null,5,6]
树结构:
1
/ | \
3 2 4
/ \
5 6
递归:
preorder(1):
result.add(1)
preorder(3):
result.add(3)
preorder(5): result.add(5)
preorder(6): result.add(6)
preorder(2): result.add(2)
preorder(4): result.add(4)
结果: [1,3,5,6,2,4]
迭代:
初始: stack = [1]
步骤1: pop=1, result=[1], push=[4,2,3] → stack=[4,2,3]
步骤2: pop=3, result=[1,3], push=[6,5] → stack=[4,2,6,5]
步骤3: pop=5, result=[1,3,5], push=[] → stack=[4,2,6]
步骤4: pop=6, result=[1,3,5,6], push=[] → stack=[4,2]
步骤5: pop=2, result=[1,3,5,6,2], push=[] → stack=[4]
步骤6: pop=4, result=[1,3,5,6,2,4], push=[] → stack=[]
结果: [1,3,5,6,2,4]
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 构建N叉树
Node buildTree(Integer[] arr) {
if (arr.length == 0 || arr[0] == null) return null;
Node root = new Node(arr[0]);
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
int i = 1;
while (!queue.isEmpty() && i < arr.length) {
Node node = queue.poll();
List<Node> children = new ArrayList<>();
// 收集当前节点的所有子节点(直到遇到null)
while (i < arr.length && arr[i] != null) {
Node child = new Node(arr[i]);
children.add(child);
queue.offer(child);
i++;
}
i++; // 跳过null分隔符
node.children = children;
}
return root;
}
// 打印列表
void printList(List<Integer> list) {
System.out.println(list);
}
// 测试用例1:标准示例
Node root1 = buildTree(new Integer[]{1,null,3,2,4,null,5,6});
List<Integer> result1 = solution.preorder(root1);
System.out.print("Test 1: ");
printList(result1); // [1,3,5,6,2,4]
// 测试用例2:复杂树
Node root2 = buildTree(new Integer[]{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});
List<Integer> result2 = solution.preorder(root2);
System.out.print("Test 2: ");
printList(result2); // [1,2,3,6,7,11,14,4,8,12,5,9,13,10]
// 测试用例3:单节点
Node root3 = new Node(1);
List<Integer> result3 = solution.preorder(root3);
System.out.print("Test 3: ");
printList(result3); // [1]
// 测试用例4:空树
List<Integer> result4 = solution.preorder(null);
System.out.print("Test 4: ");
printList(result4); // []
// 测试用例5:只有根节点和子节点
Node root5 = new Node(1, Arrays.asList(
new Node(2), new Node(3), new Node(4)
));
List<Integer> result5 = solution.preorder(root5);
System.out.print("Test 5: ");
printList(result5); // [1,2,3,4]
// 测试用例6:深度为3的树
Node root6 = new Node(1, Arrays.asList(
new Node(2, Arrays.asList(new Node(3), new Node(4))),
new Node(5, Arrays.asList(new Node(6)))
));
List<Integer> result6 = solution.preorder(root6);
System.out.print("Test 6: ");
printList(result6); // [1,2,3,4,5,6]
// 测试用例7:空子节点列表
Node root7 = new Node(1);
root7.children = new ArrayList<>(); // 空列表
List<Integer> result7 = solution.preorder(root7);
System.out.print("Test 7: ");
printList(result7); // [1]
// 测试用例8:null子节点
Node root8 = new Node(1);
root8.children = null;
List<Integer> result8 = solution.preorder(root8);
System.out.print("Test 8: ");
printList(result8); // [1]
}
关键点
-
遍历顺序:
- 前序遍历:根 → 子节点(从左到右)
- 必须按从左到右的顺序处理子节点
-
迭代的子节点压栈顺序:
- 逆序压栈(从右到左),保证弹出时是左到右
-
边界处理:
- 空树返回空列表
- 叶子节点的children可能为null或空列表
-
空间复杂度:
- 平衡树:O(log n)
- 线性树:O(n)
常见问题
-
为什么迭代要逆序压栈?
- 栈是后进先出的数据结构
- 如果按正常顺序压栈(左到右),弹出时会变成右到左
- 逆序压栈确保访问顺序是从左到右
-
如何实现后序遍历?
- 递归:先遍历所有子节点,再访问根节点
- 迭代:可以使用两个栈,或者修改访问时机
215

被折叠的 条评论
为什么被折叠?



