方法1:递归方法
思路与算法
首先我们需要了解什么是二叉树的前序遍历:按照访问根节点——左子树——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。
定义 preorder(root) 表示当前遍历到 root 节点的答案。按照定义,我们只要首先将 root 节点的值加入答案,然后递归调用 preorder(root.left) 来遍历 root 节点的左子树,最后递归调用 preorder(root.right) 来遍历 root 节点的右子树即可,递归终止的条件为碰到空节点。
class Solution {
public List preorderTraversal(TreeNode root) {
//递归方法
List res = new ArrayList();
preorder(root,res);
return res;
}
public void preorder(TreeNode root,List res){
if(root == null){
return;
}else{
res.add(root.val);
preorder(root.left,res);
preorder(root.right,res);
}
}
}
复杂度分析
时间复杂度:O(n),其中 nn 是二叉树的节点数。每一个节点恰好被遍历一次。
空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。
方法:2:迭代
思路与算法
我们也可以用迭代的方式实现方法一的递归函数,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同。
class Solution {
public List preorderTraversal(TreeNode root) {
//迭代方法
List res = new ArrayList();
if(root == null){
return res;
}
Deque stack = new LinkedList();
while(!stack.isEmpty() || root != null){
while(root != null){
res.add(root.val);
stack.push(root);
root = root.left;
}
root = stack.pop();
root = root.right;
}
return res;
}
}
复杂度分析
时间复杂度:O(n),其中 nn 是二叉树的节点数。每一个节点恰好被遍历一次。
空间复杂度:O(n),为迭代过程中显式栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。
方法1:递归
思路与算法
首先我们需要了解什么是二叉树的中序遍历:按照访问左子树——根节点——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。
定义 inorder(root) 表示当前遍历到root 节点的答案,那么按照定义,我们只要递归调用 inorder(root.left) 来遍历root 节点的左子树,然后将root 节点的值加入答案,再递归调用inorder(root.right) 来遍历 root 节点的右子树即可,递归终止的条件为碰到空节点。
class Solution {
public List inorderTraversal(TreeNode root) {
//递归方法
List res = new ArrayList();
inorder(root,res);
return res;
}
public void inorder(TreeNode root,List res){
if(root == null){
return;
}else{
inorder(root.left,res);
res.add(root.val);
inorder(root.right,res);
}
}
}
复杂度分析
时间复杂度:O(n),其中 nn 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次。
空间复杂度:O(n)。空间复杂度取决于递归的栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别。
方法2:迭代
class Solution {
public List inorderTraversal(TreeNode root) {
//迭代方法
List res = new ArrayList();
if(root == null){
return res;
}
Deque stack = new LinkedList();
while(!stack.isEmpty() || root != null){
while(root != null){
stack.push(root);
root = root.left;
}
root = stack.pop();
res.add(root.val);
root = root.right;
}
return res;
}
}
复杂度分析
时间复杂度:O(n),其中 nn 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次。
空间复杂度:O(n)。空间复杂度取决于栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别。
方法1:递归
思路与算法
首先我们需要了解什么是二叉树的后序遍历:按照访问左子树——右子树——根节点的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。
定义 postorder(root) 表示当前遍历到 root 节点的答案。按照定义,我们只要递归调用 postorder(root->left) 来遍历 root 节点的左子树,然后递归调用 postorder(root->right) 来遍历 root 节点的右子树,最后将 root 节点的值加入答案即可,递归终止的条件为碰到空节点。
class Solution {
public List postorderTraversal(TreeNode root) {
//递归方法
List res = new ArrayList();
postorder(root,res);
return res;
}
public void postorder(TreeNode root,List res){
if(root == null){
return;
}else{
postorder(root.left,res);
postorder(root.right,res);
res.add(root.val);
}
}
}
复杂度分析
时间复杂度:O(n),其中 nn 是二叉搜索树的节点数。每一个节点恰好被遍历一次。
空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。
方法2:迭代
class Solution {
public List postorderTraversal(TreeNode root) {
//迭代方法
List res = new ArrayList();
if(root == null){
return res;
}
Deque stack = new LinkedList();
TreeNode prev = null;//由于在某棵子树访问完之后,接着就要回溯到其父节点,因此使用prev来记录访问历史,在回溯到父节 点时,可以由此来判断,上一个访问的节点是否为右子树
while(!stack.isEmpty() || root!=null){
while(root!=null){
stack.push(root);
root = root.left;
}
root = stack.pop();//从栈中弹出的元素,左子树一定访问完了
//现在需要确定的是是否有右子树,或者右子树是否访问过
//如果没有右子树,或者右子树访问完了,也就是上一个访问的节点是右子节点时,说明可以访问当前根节点
if(root.right == null || prev==root.right){
res.add(root.val);
prev = root; //更新历史访问记录,这样回溯的时候父节点可以由此判断右子树是否访问完成
root = null;
}else{
//如果右子树没有被访问,那么将当前节点压栈,访问右子树
stack.push(root);
root = root.right;
}
}
return res;
}
}
复杂度分析
时间复杂度:O(n),其中 nn 是二叉搜索树的节点数。每一个节点恰好被遍历一次。
空间复杂度:O(n),为迭代过程中显式栈的开销,平均情况下为O(logn),最坏情况下树呈现链状,为 O(n)。
使用的是广度优先搜索(BFS)
乍一看来,二叉树层序遍历顺序和 BFS 是一样的,我们可以直接用 BFS 得出层序遍历结果。然而,层序遍历要求的输入结果和 BFS 是不同的。层序遍历要求我们区分每一层,也就是返回一个二维数组。而 BFS 的遍历结果是一个一维数组,无法区分每一层。
那么,怎么给 BFS 遍历的结果分层呢?我们首先来观察一下 BFS 遍历的过程中,结点进队列和出队列的过程:
截取 BFS 遍历过程中的某个时刻:
可以看到,此时队列中的结点是 3、4、5,分别来自第 1 层和第 2 层。这个时候,第 1 层的结点还没出完,第 2 层的结点就进来了,而且两层的结点在队列中紧挨在一起,我们无法区分队列中的结点来自哪一层。
因此,我们需要稍微修改一下代码,在每一层遍历开始前,先记录队列中的结点数量 n(也就是这一层的结点数量),然后一口气处理完这一层的 n个结点。
这样,我们就将 BFS 遍历改造成了层序遍历。在遍历的过程中,结点进队列和出队列的过程为:
可以看到,在 while 循环的每一轮中,都是将当前层的所有结点出队列,再将下一层的所有结点入队列,这样就实现了层序遍历。
最终我们得到的题解代码为:
class Solution {
public List> levelOrder(TreeNode root) {
List> res = new ArrayList<>();
Queue queue = new ArrayDeque<>();
if(root!= null){
queue.add(root);
}
while(! queue.isEmpty()){
int n = queue.size();
List level = new ArrayList<>();
for(int i = 0;i < n;i++){
TreeNode node = queue.poll();
level.add(node.val);
if(node.left!= null){
queue.add(node.left);
}
if(node.right!= null){
queue.add(node.right);
}
}
res.add(level);
}
return res;
}
}
复杂度分析
记树上所有节点的个数为 n。
时间复杂度:每个点进队出队各一次,故渐进时间复杂度为 O(n)。
空间复杂度:队列中元素的个数不超过 n个,故渐进空间复杂度为O(n)。