赞
踩
方法1:递归方法
思路与算法
首先我们需要了解什么是二叉树的前序遍历:按照访问根节点——左子树——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。
定义 preorder(root) 表示当前遍历到 root 节点的答案。按照定义,我们只要首先将 root 节点的值加入答案,然后递归调用 preorder(root.left) 来遍历 root 节点的左子树,最后递归调用 preorder(root.right) 来遍历 root 节点的右子树即可,递归终止的条件为碰到空节点。
- class Solution {
- public List<Integer> preorderTraversal(TreeNode root) {
- //递归方法
- List<Integer> res = new ArrayList<Integer>();
- preorder(root,res);
- return res;
- }
- public void preorder(TreeNode root,List<Integer> 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<Integer> preorderTraversal(TreeNode root) {
-
- //迭代方法
- List<Integer> res = new ArrayList<Integer>();
- if(root == null){
- return res;
- }
- Deque<TreeNode> stack = new LinkedList<TreeNode>();
-
- 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<Integer> inorderTraversal(TreeNode root) {
- //递归方法
- List<Integer> res = new ArrayList<Integer>();
- inorder(root,res);
- return res;
- }
- public void inorder(TreeNode root,List<Integer> 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<Integer> inorderTraversal(TreeNode root) {
-
- //迭代方法
- List<Integer> res = new ArrayList<Integer>();
- if(root == null){
- return res;
- }
-
- Deque<TreeNode> stack = new LinkedList<TreeNode>();
- 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<Integer> postorderTraversal(TreeNode root) {
- //递归方法
- List<Integer> res = new ArrayList<Integer>();
- postorder(root,res);
- return res;
- }
- public void postorder(TreeNode root,List<Integer> 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<Integer> postorderTraversal(TreeNode root) {
-
- //迭代方法
- List<Integer> res = new ArrayList<Integer>();
- if(root == null){
- return res;
- }
- Deque<TreeNode> stack = new LinkedList<TreeNode>();
- 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<List<Integer>> levelOrder(TreeNode root) {
- List<List<Integer>> res = new ArrayList<>();
- Queue<TreeNode> queue = new ArrayDeque<>();
-
- if(root!= null){
- queue.add(root);
- }
- while(! queue.isEmpty()){
- int n = queue.size();
- List<Integer> 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)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。