一、题目描述
给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3] 输出:[1,2,3]
示例 2:
输入:root = [] 输出:[]
示例 3:
输入:root = [1] 输出:[1]
示例 4:
输入:root = [1,2] 输出:[1,2]
示例 5:
输入:root = [1,null,2] 输出:[1,2]
提示:
- 树中节点数目在范围
[0, 100]
内 -100 <= Node.val <= 100
二、方法一:递归方法
(一)解题思路
递归方法是最直观的,按照前序遍历的顺序,递归地访问每个节点:
- 如果当前节点为空,返回。
- 访问当前节点,将节点的值添加到结果列表中。
- 递归地前序遍历左子树。
- 递归地前序遍历右子树。
(二)具体代码
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
preorder(root, result);
return result;
}
private void preorder(TreeNode node, List<Integer> result) {
if (node == null) {
return;
}
result.add(node.val); // 访问根节点
preorder(node.left, result); // 遍历左子树
preorder(node.right, result); // 遍历右子树
}
}
(三)时间复杂度和空间复杂度
1. 时间复杂度
- 原因:递归方法访问树中每个节点一次。
- 计算:对于具有
N
个节点的二叉树,每个节点都恰好被访问一次。 - 结果:时间复杂度为
O(N)
,其中N
是二叉树中节点的数量。
2. 空间复杂度
- 原因:递归方法使用栈空间来存储递归调用的信息,其大小取决于树的高度。
- 最坏情况:如果树完全不平衡,每个节点只有左子节点或只有右子节点,递归栈的深度将达到
N
。 - 最好情况:如果树是完全平衡的,递归栈的深度将是
logN
。 - 额外空间:代码中没有使用除了递归栈以外的额外空间。
- 结果:空间复杂度介于
O(logN)
和O(N)
之间,取决于树的形状。额外空间复杂度是O(1)
。
3. 总结
- 时间复杂度:
O(N)
- 空间复杂度:
O(1)
(额外空间),O(logN)
到O(N)
(递归栈空间)
(四)总结知识点
-
递归:这是一种编程技巧,允许函数调用自身。在这个代码中,
preorder
函数会递归地调用自身来遍历二叉树的每个节点。 -
二叉树遍历:代码实现了二叉树的前序遍历,这是一种深度优先遍历策略,按照“根-左-右”的顺序访问树的节点。
-
二叉树节点定义:代码中使用了
TreeNode
类来定义二叉树的节点,每个节点包含一个整数值val
和两个指向其左右子节点的指针left
和right
。 -
Java集合框架:代码使用了
ArrayList
来存储遍历的结果。ArrayList
是Java集合框架中的一个可调整大小的数组实现,用于存储对象列表。 -
函数参数传递:代码中的
preorder
函数接受一个TreeNode
类型的参数和一个List<Integer>
类型的参数,这展示了如何在Java中传递和修改对象引用。 -
基本语法结构:代码包含了基本的Java语法结构,如类的定义、方法的定义、条件语句(
if
)、返回语句(return
)和列表的添加操作(result.add
)。 -
递归的基本条件:在
preorder
函数中,递归的基本条件是当遇到一个null
节点时返回,这避免了递归调用的无限循环。 -
方法重载:
Solution
类中有两个名为preorder
的方法,但它们的参数列表不同,这是Java方法重载的例子。一个方法是公共的,用于外部调用,另一个方法是私有的,作为辅助方法用于递归遍历。
三、方法二:迭代方法
(一)解题思路
迭代方法通常使用栈来模拟递归过程:
- 创建一个空栈,将根节点压入栈中。
- 当栈不为空时,弹出栈顶元素,访问该节点,并将其值添加到结果列表中。
- 先将弹出节点的右子节点(如果有)压入栈中,然后将左子节点(如果有)压入栈中。这样可以保证左子节点先被访问。
- 重复步骤2和3,直到栈为空。
(二)具体代码
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val); // 访问节点
if (node.right != null) {
stack.push(node.right); // 右子节点先入栈
}
if (node.left != null) {
stack.push(node.left); // 左子节点后入栈
}
}
return result;
}
}
(三)时间复杂度和空间复杂度
1. 时间复杂度
- 原因:迭代方法访问树中每个节点一次。
- 计算:对于具有
N
个节点的二叉树,每个节点都恰好被访问一次。 - 结果:时间复杂度为
O(N)
,其中N
是二叉树中节点的数量。
2. 空间复杂度
- 原因:迭代方法使用栈空间来存储待访问的节点,其大小取决于树的高度。
- 最坏情况:如果树完全不平衡,每个节点只有左子节点或只有右子节点,栈的深度将达到
N
。 - 最好情况:如果树是完全平衡的,栈的深度将是
logN
。 - 结果:空间复杂度介于
O(logN)
和O(N)
之间,取决于树的形状。
3. 总结
- 时间复杂度:
O(N)
- 空间复杂度:
O(logN)
到O(N)
(四)总结知识点
-
迭代方法:与递归方法不同,迭代方法使用栈来模拟递归过程,用于遍历二叉树的节点。
-
栈数据结构:代码使用了
Stack
类来存储待访问的节点。栈是一种后进先出(LIFO)的数据结构,用于在迭代过程中保持节点的访问顺序。 -
二叉树遍历:代码实现了二叉树的前序遍历,按照“根-左-右”的顺序访问树的节点。
-
二叉树节点定义:代码中使用了
TreeNode
类来定义二叉树的节点,每个节点包含一个整数值val
和两个指向其左右子节点的指针left
和right
。 -
Java集合框架:代码使用了
ArrayList
来存储遍历的结果。ArrayList
是Java集合框架中的一个可调整大小的数组实现,用于存储对象列表。 -
条件语句:代码中的
if
语句用于检查当前节点是否有左右子节点,以便将它们添加到栈中。 -
循环结构:
while
循环用于在栈不为空的情况下继续遍历二叉树的节点。 -
基本语法结构:代码包含了基本的Java语法结构,如类的定义、方法的定义、栈的操作(
push
和pop
)以及列表的添加操作(result.add
)。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。