二叉树--经典面试题2

目录

1.根据一棵树的前序遍历与中序遍历构造二叉树

2.根据一棵树的中序遍历与后序遍历构造二叉树

3.二叉树创建字符串

 4.二叉树前序非递归遍历实现

5.二叉树中序非递归遍历实现

6.二叉树后序非递归遍历实现

7.二叉树的最大宽度

8.合并二叉树


1.根据一棵树的前序遍历与中序遍历构造二叉树

题目描述

 代码示例

class Solution {
    public Map<Integer,Integer> map = null;
    // 遍历 preorder 数组,不能作为递归参数,否则递完左树归回来的时候,回退成 0 了
    public int preIndex = 0;

    public TreeNode buildTreeChild(int[] preorder, int[] inorder, int inLeft, int inRight) {
        if(inLeft > inRight) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[preIndex]);

        // 找根节点在中序遍历中的位置
        int rootIndex = map.get(preorder[preIndex]);
        preIndex++;

        root.left = buildTreeChild(preorder,inorder,inLeft,rootIndex - 1);
        root.right = buildTreeChild(preorder,inorder,rootIndex + 1,inRight);

        return root;
    }
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n = inorder.length;
        map = new HashMap<Integer,Integer>();
        for(int i = 0;  i < n; i++) {
            map.put(inorder[i],i);
        }
        return buildTreeChild(preorder,inorder,0,n - 1);
    }
}

画图理解

解题思路

🍃1.定义 preIndex 遍历前序遍历这个数组

🍃2.在中序遍历的 inLeft - inRight 之间,找到 前序遍历元素 的位置

🍃3.此时 preIndex 左边的就是左树,preIndex 右边的就是右树

🍃4.递归创建左树和递归创建右树

🍃5.当 inLeft > inRight 的时候,说明没有了左树或者右树

复杂度分析

🍃时间复杂度:O(N),遍历树中结点个数。

🍃空间复杂度:O(N),递归的栈空间 O(h),h < n,所以总空间复杂度为 O(N)


2.根据一棵树的中序遍历与后序遍历构造二叉树

题目描述

代码示例

class Solution {
    public Map<Integer,Integer> map;
    public int postIndex;
    
    // 后续遍历序列从 length - 1 位置开始遍历,根,右,左的顺序好建树
    public TreeNode buildTreeChild(int[] inorder,int[] postorder,int inLeft,int inRight) {
        if(inLeft > inRight) {
            return null;
        }
        // 根
        TreeNode root = new TreeNode(postorder[postIndex]);

        // 找根节点在中序遍历中的位置
        int rootIndex = map.get(postorder[postIndex]);
        postIndex--;

        // 递归右树
        root.right = buildTreeChild(inorder,postorder,rootIndex + 1,inRight);
        // 递归左树
        root.left = buildTreeChild(inorder,postorder,inLeft,rootIndex - 1);

        return root;
    }
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        // 从序列末尾开始往前遍历
        postIndex = postorder.length - 1;
        int n = inorder.length;
        // 存放中序遍历的元素和下标,方便找后序遍历元素在中序遍历
        map = new HashMap<Integer,Integer>();

        for(int i = 0; i < n; i++) {
            map.put(inorder[i],i);
        }
        return buildTreeChild(inorder,postorder,0,n - 1);
    }
}

解题思路

这道题和前面那道题思路一样,唯一不一样的地方就是这里的 postIndex(后续遍历序列的下标)要从序列末尾开始,这样就形成了 根、右、左 的顺序了。

复杂度分析

🍃时间复杂度:O(N),遍历树中结点个数。

🍃空间复杂度:O(N),递归的栈空间 O(h),h < n,所以总空间复杂度为 O(N)。


3.二叉树创建字符串

题目描述

代码示例

class Solution {
    public void tree2strChild(TreeNode root, StringBuilder stringBuilder) {
        if(root == null) {
            return;
        }
        // 先将根节点的 val 拼接上
        stringBuilder.append(root.val);

        // 左不为空,拼接一个 "(",一直递归完左树,为空时回退完了之后,拼上一个 ")"
        if(root.left != null) {
            stringBuilder.append("(");
            tree2strChild(root.left,stringBuilder);
            stringBuilder.append(")");
        } else {
            // 左为空,两种情况
            if(root.right == null) {
                return;
            } else {
                stringBuilder.append("()");
            }
        }

        // 左树完了,走右树
        if(root.right != null) {
            stringBuilder.append("(");
            tree2strChild(root.right,stringBuilder);
            stringBuilder.append(")");
        } else {
            return;
        }
    }
    public String tree2str(TreeNode root) {
        StringBuilder stringBuilder = new StringBuilder();

        tree2strChild(root,stringBuilder);

        return stringBuilder.toString();
    }
}

 画图理解

首先要把几种情况分清楚:

 前三种情况:

 ​​​​​​

 第四种情况 

复杂度分析 

时间复杂度:O(n),其中 n 是二叉树中的节点数目。

空间复杂度:O(n)。在最坏情况下会递归 n 层,需要 O(n) 的栈空间。


 4.二叉树前序非递归遍历实现

代码示例

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) {
            return list;
        }

        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;

        while(cur != null || !stack.empty()) {
            // 添加左树
            while(cur != null) {
                stack.push(cur);
                list.add(cur.val);
                cur = cur.left;
            }
            // 左树为空时,继续遍历右树
            TreeNode tmp = stack.pop();
            cur = tmp.right;
        }
    }
}

画图理解

 解题思路

按照前线遍历的思路,在循环里头先添加根结点,然后不断往左遍历,直到左树为空,再继续遍历右树。从添加左树转为右树时,有个难点,右树可能为空,也可能不为空,所以我们需要从栈里面弹出一个元素进行判断,但是此时我们已经出循环了,所以要在外层套上一个循环,进而分两种情况讨论:

1.cur == null,如果栈也空了,那么说明全部结点已经添加完了,如果栈不为空,就继续弹出元素 tmp,使 cur 再次指向 tmp.right;

2.cur != null,继续进内层循环,不断添加元素,进而进行判断!!

复杂度分析

时间复杂度:O(N),刚好遍历二叉树的每一个结点。

空间复杂度:O(N),最坏情况 O(N),全部进栈后再出栈。最好情况 O(1),进一个出一个。平均情况 O(logN),完全二叉树,进栈树的高度,再出栈。


5.二叉树中序非递归遍历实现

代码示例

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) {
            return list;
        }
        Stack<TreeNode> stack = new Stack<>();

        TreeNode cur = root;

        while(cur != null || !stack.empty()) {
            while(cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            // 先遍历至左为空,然后添加根,再右
            TreeNode tmp = stack.pop();
            list.add(tmp.val);
            cur = tmp.right;
        }
        return list;
    }
}

这道题与前面那道题相似,就不做过多的解释了。


6.二叉树后序非递归遍历实现

代码示例

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) {
            return list;
        }
        TreeNode prev = null;

        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;

        while(cur != null || !stack.empty()) {
            while(cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            // 此时还不能弹出栈顶元素,可能还有右孩子
            TreeNode top = stack.peek();
            if(top.right == null || prev == top.right) {
                // 如果为空就弹出,并且要记录已经添加过,不然下次又会进 else ,重复添加
                stack.pop();
                list.add(top.val);
                prev = top;
            } else {
                cur = top.right;
            }
        }
        return list;
    }
}

画图理解 

 所以这个题的难点就是每次添加完一个结点后需要记录一下!!!


7.二叉树的最大宽度

题目描述

此处的宽度,动手画图后,很容易就知道是怎么计算的:从根节点按照从上至下从左至右的顺序对所有节点从 1 开始编号。且满足:

  • 2i < n左孩子序号:2i否则无左孩
  • 2i+1 < n右孩子序号:2i+1否则无右孩子

 

代码示例

class Solution {
    //最大宽度必须定义在外面,否则每次递归都重新定义了
    int maxW;
    public int widthOfBinaryTree(TreeNode root) {
        int index = 1;//值
        int level = 1;//层数
        dfs(root,1,1,new ArrayList<>());
        return maxW;   
    }
    private void dfs(TreeNode root, int level, int index, List<Integer> leftList) {
        if(root == null) return;
        //层数大于集合元素个数,就添加-->即添加所有最左边元素
        if(level > leftList.size()) {
            leftList.add(index);
        }
        //所有除了最左边结点减去最左边的结点的差值加一就等于两者之间的宽度,不断筛选最大的宽度
        maxW = Math.max(maxW,index - leftList.get(level - 1) + 1);
        //注意index可不敢++
        dfs(root.left, level + 1, index * 2, leftList);
        dfs(root.right, level + 1, index * 2 + 1, leftList);
    }
}

🍃1. ArrayList 每次添加的元素都是二叉树每一层的最左边的元素,方便其他结点计算宽度:index - list.get(level - 1) + 1,为什么要 get(level),因为数组下标从 0 开始,然后再加上一个 1,才是真正的宽度。

🍃2.递归的时候,左右孩子的下标不要写成 index + 1,而是要按照编号后的关系来递归。

复杂度分析

时间复杂度:O(N),遍历每个结点。

空间复杂度:O(N),这部分空间是因为我们 DFS 递归过程中有 N 层的栈。


8.合并二叉树

题目描述

 代码示例

    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if(root1 == null) {
            return root2;
        }
        if(root2 == null) {
            return root1;
        }
        TreeNode newRoot = new TreeNode(root1.val + root2.val);
        newRoot.left = mergeTrees(root1.left,root2.left);//新的左子树
        newRoot.right = mergeTrees(root1.right,root2.right);//新的右子树
        return newRoot;
    }

解题思路

🍃1.在递归的过程中,当一棵树为空树时,直接返回另一棵树的根;

🍃2.如果两树的根都不为空,新的根的值就是两树根值的和。

🍃3.如果两树的左树都不为空,新的左子树就是两左子树的值相加,右子树同理。


谢谢观看!!!

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Master_hl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值