有4个节点可以构造出 二叉树_[二叉树面试算法]-(七) 重构二叉树-相关题型总结(4题)...

  1. 从前序与中序遍历序列构造二叉树_leetcode105
  2. 从中序与后序遍历序列构造二叉树_leetcode106
  3. 根据前序和后序遍历构造二叉树_leetcode889
  4. 二叉树的序列化与反序列化_leetcode297

1.从前序与中序遍历序列构造二叉树

思路:

第一步:
前序遍历的第一个节点,即为根节点。
第二步:
由根节点,可以在中序遍历中,找到左子树对应的长度。
第三步:
根据左子树的长度,
可以在先序序列和中序序列中,找到左子树和右子树对应的先序序列和中序序列。

代码如下:

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTree(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
    }
    
    public TreeNode buildTree(int[] preorder, int startPre,int endPre,
                                            int[] inorder,int startIn,int endIn) {
        if(startPre>endPre){
            return null;
        }
        TreeNode root = new TreeNode(preorder[startPre]);//创建根节点
        int i = startIn; // i最终定位的是中序序列中,根节点的下标。
        for(;i<=endIn;i++){
            if(inorder[i]==preorder[startPre]){
                break;
            }
        }
        root.left =  buildTree(preorder,startPre+1,startPre+i-startIn,inorder,startIn,i-1);
        root.right = buildTree(preorder,startPre+i-startIn+1,endPre,inorder,i+1,endIn);
        return root;    
    }
}

2.从中序和后序遍历序列构造二叉树

思路分析:

与上题类似,由后序遍历,来确定根节点的值。
然后,从中序遍历中,找到左右子树的长度及对应的序列,
递归构造即可。

代码如下:

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder==null || postorder==null){
            return null;
        }
        TreeNode root = buildTree(inorder,0,inorder.length-1,postorder,0,postorder.length-1);
        return root;     
    }
    
    public TreeNode buildTree(int[] inorder,int startIn,int endIn,int[] postorder,int startP,int endP){
        if(startIn > endIn){
            return null;
        }
        
        int rootVal = postorder[endP];
        TreeNode root = new TreeNode(rootVal);
        
        // 求中序序列中root节点的下标
        int rootIndex = startIn;
        for(;rootIndex<=endIn;rootIndex++){
            if(inorder[rootIndex]==rootVal){
                break;
            }
        }
        
        int leftLen = rootIndex - startIn;// 左子树的长度
        root.left = buildTree(inorder,startIn,startIn+leftLen-1,postorder,startP,startP+leftLen-1);
        root.right = buildTree(inorder,rootIndex+1,endIn,postorder,startP+leftLen,endP-1);
        return root;
    }
}

3.从前序和后序遍历序列构造二叉树

思路1:根据前序和后序序列及起始位置来构建二叉树。

前序遍历的第一个节点可以确定根节点.
- 前序序列中,根节点的下一个节点,为左子树的根节点。
- 由前序序列中的根节点的值,可以在后序序列中,找到对应的根节点的位置,从而确定左子树的位置。
依次迭代,便可以递归构造出二叉树。 

代码实现:

class Solution {
    public TreeNode constructFromPrePost(int[] pre, int[] post) {
        if(pre==null || post==null){
            return null;
        }
        TreeNode res = constructFromPrePost(pre,0,pre.length-1,post,0,post.length-1);
        return res;
    }
    
    public TreeNode constructFromPrePost(int[] pre,int startPre,int endPre,int[] post,int startP,int endP) {
        if(startPre > endPre){
            return null;
        }
        
        int rootVal= pre[startPre];
        TreeNode root =new TreeNode(rootVal);
        
        if(startPre+1 <=endPre){
            int leftRootVal = pre[startPre+1];// 左子树的根节点
            int leftRootIndex = startP;
            
            for(;leftRootIndex<endP;leftRootIndex++){
                if(post[leftRootIndex]==leftRootVal){
                    break;
                }
            }
            int leftLen = leftRootIndex - startP; // 左子树的长度
            root.left = constructFromPrePost(pre,startPre+1,startPre+1+leftLen,post,startP,startP+leftLen);
            root.right = constructFromPrePost(pre,startPre+1+leftLen+1,endPre,post,startP+leftLen+1,endP-1);
        }
        return root;
    }
}

拓展:

仅仅知道前序遍历和后序遍历的顺序,不能唯一确定一棵二叉树。如下图所示:

fa67bc18b0af515ba7730dce2748ca8a.png

二叉树遍历情况为

二叉树1的前序序列为[1,2,3,4],后序序列为[3,4,2,1]
二叉树2的前序序列为[1,2,3,4],后序序列为[3,4,2,1].
两棵二叉树的结构不同,但是其前序和后序的序列的结果是一样的。
所以,根据前序序列和后序序列的结果,不能唯一确定一棵二叉树。

上述题解默认的是构造二叉树1的这种情况。由于leetcode中的测试用例是按照构建二叉树的层次遍历结构来验证,构造的结果,所以,上述代码能够通过。

思路二:leetcode官方题解,易于理解

前序遍历为:
(根结点) (前序遍历左分支) (前序遍历右分支)
而后序遍历为:
(后序遍历左分支) (后序遍历右分支) (根结点)

例如,如果最终的二叉树可以被序列化的表述为 [1, 2, 3, 4, 5, 6, 7],
那么其前序遍历为 [1] + [2, 4, 5] + [3, 6, 7],而后序遍历为 [4, 5, 2] + [6, 7, 3] + [1].

果我们知道左分支有多少个结点,我们就可以对这些数组进行分组,并用递归生成树的每个分支。

算法
我们令左分支有 L 个节点。我们知道左分支的头节点为 pre[1],但它也出现在左分支的后序表示的最后。
所以 pre[1] = post[L-1](因为结点的值具有唯一性),因此 L = post.indexOf(pre[1]) + 1。
现在在我们的递归步骤中,左分支由 pre[1 : L+1] 和 post[0 : L] 重新分支,
而右分支将由 pre[L+1 : N] 和 post[L : N-1] 重新分支。

这里,巧妙的利用数学中的设未知数,设有L个节点,使得编程变得简单。是上述思路一种的简化版。

代码实现如下:

class Solution {
    public TreeNode constructFromPrePost(int[] pre, int[] post) {
        int N = pre.length;
        if(N==0) return null;
        TreeNode root = new TreeNode(pre[0]);
        if(N==1) return root;
        
        int L = 0;
        for(int i=0;i<N;i++){
            if(post[i]==pre[1])
                L = i+1;
        }
        
        root.left = constructFromPrePost(Arrays.copyOfRange(pre,1,L+1),
                                        Arrays.copyOfRange(post,0,L));
        root.right = constructFromPrePost(Arrays.copyOfRange(pre,L+1,N),
                                         Arrays.copyOfRange(post,L,N-1));
        return root;
    }
}

4.二叉树的序列化和反序列化

思路:

序列化,就是层次遍历二叉树,将遍历结果用字符串来表示。若某一节点为null,则用特殊的字符来表示,如“#”
反序列化,根据层次化遍历序列,来重构二叉树。

代码实现:

public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            if(node==null){
                sb.append("#,");
            }else{
                sb.append(node.val).append(",");
                queue.add(node.left);
                queue.add(node.right);
            }
           
        }
        sb.deleteCharAt(sb.length()-1); // 删除最后一位的字符“,”
        return sb.toString();
    }

    // Decodes your encoded data to tree. // 将上述的层次遍历的字符串,重构为二叉树
    public TreeNode deserialize(String data) {
        String[] strings = data.split(",");
        if(strings.length==1 && strings[0].equals("#")){
            return null;
        }   
        
        // 使用一个队列来保存下一层节点
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode root = new TreeNode(Integer.parseInt(strings[0]));
        queue.offer(root);
        int index = 1; // 字符串数组的下标
        while(index < strings.length){
            TreeNode node = queue.poll();
            if(node != null){
                TreeNode left = strings[index].equals("#")?null:new TreeNode(Integer.parseInt(strings[index]));
                index++;
                queue.offer(left);
                node.left = left;
                // 因为字符串是由层次遍历拼接组成的,所以能保证index+1,和index+2都在字符的长度范围内
                TreeNode right = strings[index].equals("#")?null:new TreeNode(Integer.parseInt(strings[index]));
                index++;
                queue.offer(right);
                node.right = right;
            }
        }
        return root;
    }
}

结语

以上,便是关于二叉树与二叉树结构之间的四类题目总结。

从上面可知,有序列化的某一排序序列,可以反序列化,重构实际的二叉树。因为在序列化的过程中,是用了特殊的字符,来记录null节点的信息。

如果没有用特殊的符号来记录空信息,则需要通过两种方式的遍历序列,才能重构二叉树。

前序遍历+中序遍历,可以唯一确定一棵二叉树。

后序遍历+中序遍历,也可以唯一确定一棵二叉树。

但是,前序遍历+后序遍历,不能唯一确定一棵二叉树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值