剑指 Offer 07. 重建二叉树

题目链接
参考Krahets题解
先序遍历顺序: [根节点|左子树|右子树]
中序遍历顺序: [左子树|根节点|右子树]
例如题目中

  • 前序遍历划分 [ 3 | 9 | 20 15 7 ]
  • 中序遍历划分 [ 9 | 3 | 15 20 7 ]

基于这两个序列的性质,我们可以设计如下算法

  1. 按序访问前序遍历序列,得到根节点root
  2. 在中序遍历序列查找root,得到[左子树,root,右子树]
  3. 根据2中得到的左右子树长度,可以将前序遍历序列划分为[root,左子树,右子树]

这里步骤2中查找root的方法有两种,一种是直接按序查找中序遍历序列,时间复杂度为 O ( n ) O(n) O(n),另一种则是根据哈希表查找,时间复杂度为 O ( 1 ) O(1) O(1)。会得到不同的算法复杂度。

按序查找的方法
这里的preorderinorder数组反复不变地出现在递归参数中,应该设置为成员变量,避免过长的递归参数。造成递归内存的浪费

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder == null || inorder == null || preorder.length == 0 || inorder.length == 0
        || preorder.length != inorder.length)
            return null;
        return buildTree(preorder, inorder, 0, preorder.length - 1, 0, inorder.length - 1);
    }

    private TreeNode buildTree(int[] preorder, int[] inorder, int startPreorderIndex, 
    int endPreorderIndex, int startInorderIndex, int endInorderIndex){
        // 新建节点的值,从先序遍历中取出对应的值
        int rootVal = preorder[startPreorderIndex];
        TreeNode root = new TreeNode(rootVal);
        
        if(startPreorderIndex == endPreorderIndex)
            return root;
        
        // 在中序遍历序列中找到当前节点的值
        int rootIndex = startInorderIndex;
        while(rootIndex < endInorderIndex && inorder[rootIndex] != rootVal)
            rootIndex++;
        if(rootIndex == endInorderIndex && inorder[rootIndex] != rootVal)
            return null;
        // 记录左子树的长度
        int leftLength = rootIndex - startInorderIndex;
        // 记录先序遍历中的终点
        int leftPreorderIndex = startPreorderIndex + leftLength;
        
        // 构建左子树, 取出先序遍历中与左子树等长的序列,取出中序遍历中被root分割的左半部分
        if(leftLength > 0){
            root.left = buildTree(preorder, inorder, startPreorderIndex + 1, leftPreorderIndex, 
            startInorderIndex, rootIndex - 1);
        }
        // 构建右子树,取出先序遍历序列除了左子树外剩余的部分,取出中序遍历中被root分割的右半部分
        if(leftLength < endPreorderIndex - startPreorderIndex){
            root.right = buildTree(preorder, inorder, leftPreorderIndex + 1, endPreorderIndex,
            rootIndex + 1, endInorderIndex);
        }
        return root;   
    }
}

算法复杂度:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2),需要建立 O ( n ) O(n) O(n)个节点,每个节点都要对中序遍历序列进行搜索。搜索一次需要 O ( n ) O(n) O(n)
  • 空间复杂度,最坏的情况下,树退化成链表,递归深度达到 O ( n ) O(n) O(n),占用 O ( N ) O(N) O(N)额外空间,最好的情况是满二叉树递归深度为 O ( l o g n ) O(logn) O(logn),需要 O ( l o g n ) O(logn) O(logn)的空间。

利用哈希表查找的方法

class Solution {
    int[] preorder;
    Map<Integer, Integer> dic = new HashMap<>();
    
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder == null || inorder == null || preorder.length == 0 || inorder.length == 0
        || preorder.length != inorder.length)
            return null;
        this.preorder = preorder;
        for(int i = 0; i < inorder.length; i++){
            dic.put(inorder[i], i);
        }
        return buildTree(0, 0, preorder.length - 1);
    }

    // root用于记录当前访问的先序数组的下标,left和right分别记录后序遍历序列的开始和结束下标
    private TreeNode buildTree(int root, int left, int right){
        if(left > right)
            return null;
        TreeNode node = new TreeNode(preorder[root]);
        int rootIndex = dic.get(preorder[root]);
        node.left = buildTree(root + 1, left, rootIndex - 1);           
        node.right = buildTree(root + 1 + rootIndex - left, rootIndex + 1, right);
        return node;
    }
}

算法复杂度

  • 时间复杂度:需要建立 O ( n ) O(n) O(n)个节点,需要遍历后序序列,复杂度均为 O ( n ) O(n) O(n)
  • 空间复杂度:建立哈希表需要 O ( n ) O(n) O(n)的空间;最坏的情况下,树退化成链表,递归深度达到 O ( n ) O(n) O(n),占用 O ( n ) O(n) O(n)额外空间,最好的情况是满二叉树递归深度为 O ( l o g n ) O(logn) O(logn),需要 O ( l o g n ) O(logn) O(logn)的空间。

有大佬写出了迭代的算法。题解链接,回头分析分析

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder == null || preorder.length == 0) {
            return null;
        }
        int length = preorder.length;
        TreeNode root = new TreeNode(preorder[0]);
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);/*将根节点压栈*/
        int inIndex = 0;
        for (int i = 1; i < length; i++) {
            TreeNode node = stack.peek();/*取出栈顶的结点*/
            int currPreVal = preorder[i];/*暂时存放下个节点的值*/

            if (node.val != inorder[inIndex]){/*寻找根节点,如果不等于,说明仍处于根节点的左子树中*/
                TreeNode left = new TreeNode(currPreVal);
                node.left = left;
                stack.push(left);
            }else {
                while (!stack.isEmpty() && stack.peek().val==inorder[inIndex]){/*当前树弹出左子树和根节点*/
                    node = stack.pop();
                    inIndex++;
                }
                TreeNode right = new TreeNode(currPreVal);
                node.right = right;
                stack.push(right);
            }
        }
        return root;
    }
}

C++递归实现

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        return rebuild(pre, vin, 0, pre.size() - 1, 0, vin.size() - 1);
    }
    
    TreeNode* rebuild(vector<int>& pre, vector<int>& vin, int pre_left, 
                      int pre_right, int vin_left, int vin_right){
        if(pre_left > pre_right)
             return nullptr;
        TreeNode* root = new TreeNode(pre[pre_left]);
        for(int i = vin_left; i <= vin_right; i++){
            if(vin[i] == root->val){
                root->left = rebuild(pre, vin, pre_left + 1, pre_left + i - vin_left, vin_left, i - 1);
                root->right = rebuild(pre, vin, pre_left + i - vin_left + 1, pre_right, i + 1, vin_right);
                break;
            }
        }
        return root;
    }
    
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值