题目描述

系统中有一棵n个点的完全二叉树,现给出它的BFS层序遍历序列(从根节点开始,自上而下自左到右的一层一层遍历,即首先访问根,然后从左到右访问第2层上的节点,接着是第三层的节点),请你还原这棵树,并返回加密后的答案。
答案加密方法:所有边两个端点异或的和,图片说明 ,其中(ui, vi))为一条树上的边。
完全二叉树:若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k层所有的结点都连续集中在最左边。
样例构成的完全二叉树为:
图片说明

题目分析

因为完全二叉树的特点,n-1层是满二叉树,而第n层是按照从左到右的顺序,因为满二叉树中,非叶子结点都有两个子节点,易知子节点的下标与父节点下标存在两倍的关系,通过对样例分析可以得到以下结论:
当结点 i 满足 2i+1 < n时,说明该结点有左子节点,且左子节点下标为2i+1;
当结点 i 满足 2i+2 < n时,说明该结点有右子节点,且左子节点下标为2i+2;
图片说明
题目中所示的加密方法主要是遍历树,将所有父节点和子节点的值进行异或相加,即为加密结果。

解题思路

方法1:重构二叉树,递归遍历树
可以根据数组,对完全二叉树进行重构,然后再对这个树进行深度遍历,计算异或和。
重构过程与dfs过程相似,关键是要确定根结点和左右子节点的下标位置:

buildeTree(int[] a, int index){
    TreeNode root = new TreeNode(index);
    root.left = buildTree(a, index*2+1);
    root.right= buildTree(a, index*2+2);
}

方法2:根据完全二叉树的特点,直接遍历数组进行计算
根据上述分析,可以不用构建树,直接根据下标访问结点值,进行异或运算;
根节点和左子节点的异或值:a[i] ^ a[2i+1];
根节点和右子节点的异或值:a[i] ^ a[2i+2];
将所有的异或值加起来,即为结果。

代码实现

方法1:重构二叉树,递归遍历树

// 定义树结构
class TreeNode{
    TreeNode left;
    TreeNode right;
    int val;
    TreeNode(int val){
        this.val = val;
    }
}
    long res = 0;
    public long tree1 (int[] a) {
        // write code here
        // 构建树
        TreeNode root = buildTree(a, 0);
        // 计算加密异或和
        countSum(root);
        return res;
    }

    public TreeNode buildTree(int[] a, int index){
        if(index >= a.length) return null;
        // 创建根节点
        TreeNode root = new TreeNode(a[index]);
        // 根据完全二叉树的特点,获得根节点的左右子节点
        root.left = buildTree(a, index*2+1);
        root.right = buildTree(a, index*2+2);
        return root;
    }

    public void countSum(TreeNode root){
        if(root == null) return;
        if(root.left != null){
            // 计算根和左子节点的异或和
            res += root.val ^ root.left.val;
        }
        if(root.right != null){
            // 计算根和左子节点的异或和
            res += root.val ^ root.right.val;
        }
        // 递归遍历剩下的结点
        countSum(root.left);
        countSum(root.right);
    }

时间复杂度:,因为构建二叉树和计算异或和的过程都需要遍历整个树,时间花费为2n;
空间复杂度:,在递归过程中,栈的深度最多为n,空间花费为n;

方法2:根据完全二叉树的特点,直接遍历数组进行计算

    public long tree1 (int[] a) {
        // write code here
        long res = 0;
        for(int i=0;2*i+1<a.length;i++){
            // 确定 i 结点的左子节点 2*i+1,获取边的端点异或值
            res += a[i] ^ a[2*i+1];
            if(2*i+2<a.length){
                // 确定 i 结点的右子节点
                res += a[i] ^ a[2*i+2];
            }
        }
        return res;
    }

时间复杂度:,需要遍历一半的数组,时间花费为n/2;
空间复杂度:,需要一个变量来记录计算结果。