BinaryTree_4_构造

  1. 二叉树的构造类问题
  2. 654.最大二叉树
  3. 105.从前序与中序遍历序列构造二叉树
  4. 106.从中序与后序遍历序列构造二叉树
  5. 889.根据前序和后序遍历构造二叉树

二叉树的构造类问题

1.这类问题主要是从已有数组里面构造二叉树
2.主要思想是按照要求找到根节点, 然后以某种形式递归地构建左右子树

654.最大二叉树

给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树 。

示例 1:输入:nums = [3,2,1,6,0,5] 输出:[6,3,5,null,2,0,null,null,1] 解释:递归调用如下所示:

  • [3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。
    • [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
      • 空数组,无子节点。
      • [2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。
        • 空数组,无子节点。
        • 只有一个元素,所以子节点是一个值为 1 的节点。
    • [0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。
      • 只有一个元素,所以子节点是一个值为 0 的节点。
      • 空数组,无子节点。

示例 2:输入:nums = [3,2,1] 输出:[3,null,2,null,1]

分析:
1.直接按照规则扫描数组拿到最大值, 然后递归构造左右子树, 伪代码思路如下:

TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
  if (nums.empty()) {
    return nullptr;
  }
  int maxIdx = 0;
  int maxValue = INT_MIN;
  for (int i = 0; i < nums.size(); ++i) {
    if (nums[i] > maxValue) {
      maxIdx = i;
      maxValue = nums[i];
    }
  }
  TreeNode* root = new TreeNode(maxValue);
  root->left = constructMaximumBinaryTree(nums[0..maxIdx-1]);              // 每次递归的时候这里的数组索引需要更新
  root->right = constructMaximumBinaryTree(nums[maxIdx+1..nums.size()-1]); // 每次递归的时候这里的数组索引需要更新
  return root;
}

每次递归构建左右子树的时候, 需要的数组索引区间都是在变更的, 因此需要引入一个辅助函数, 去每次递归的时候带上构建子树对应的数组内索引的起点和终点

完整答案如下

class Solution {
 public:
  TreeNode* build(vector<int>& nums, int low, int high) {
    if (low > high) {
      return nullptr;
    }
    int maxIdx = 0;
    int maxValue = INT_MIN;
    for (int i = low; i <= high; ++i) {
      if (nums[i] > maxValue) {
        maxIdx = i;
        maxValue = nums[i];
      }
    }
    TreeNode* root = new TreeNode(maxValue);
    root->left = build(nums, low, maxIdx-1);
    root->right = build(nums, maxIdx+1, high);
    return root;
  }
  TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
    if (nums.empty()) {
      return nullptr;
    }
    return build(nums, 0, nums.size()-1);
  }
};

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

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1: 输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] 输出: [3,9,20,null,null,15,7]

示例 2: 输入: preorder = [-1], inorder = [-1] 输出: [-1]

分析:
1.先拿一个例子看,

        1
       / \
     2     3
    / \   / \ 
   5   4 8   9 
      / \
     6   7

preorder
1,[2,5,4,6,7],[3,8,9]
inorder
[5,2,6,4,7],1,[8,3,9]
根节点把 inorder 可以划分成两半, 然后继续递归建立树, 每次建立树在先序数组里面维护起点和终点, 在中序里面也要维护起点和终点; 所以思路是先找根节点 => 左右子树递归建树, 建树的过程需要算各种长度, 我们先写个代码框架:

TreeNode* build(vector<int>& preorder, vector<int>& inorder, int preLow, int preHigh, int inLow, int inHigh) {
  if (preLow > preHigh) {
    return nullptr;
  }
  // 找到先序的第一个就是根节点
  int rootVal = preorder[preLow];
  int rootInorderIdx;
  for (int i = inLow; i <= inHigh; ++i) {
    if (inorder[i] == rootVal) {
      rootInorderIdx = i;
      break;
    }
  }
  TreeNode* root = newTreeNode(rootVal);
  root->left = build(preorder, inorder, ?, ?, ?, ?);
  root->right = build(preorder, inorder, ?, ?, ?, ?);
  return root;
}

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
  return build(preorder, inorder, 0, preorder.size()-1, inorder.size()-1)
}

现在需要明确上面递归构建子树的时候index怎么填, 如果能填对相应的4个index, 那么就完成了; 我们发现在中序序列里面是比较好填的, 无非就是左侧给到左子树, 右侧给到右子树

TreeNode* root = newTreeNode(rootval);
root->left = build(preorder, inorder, ?, ?, inlow, rootInorderIdx-1)  
root->right = build(preorder, inorder, ?, ?, rootInorderIdx+1, inHigh)

那么先序的左边起止点怎么填?
先序左边的起点是 preLow+1, 先序的终点呢? 其实是要找到先序左侧对应的 size 有多大, 然后 preLow + leftSize;
先序右边的终点是 preHigh, 先序的起点呢? 顺着上面的结论, 其实就是左侧那个区间的后一个节点, 可以得到 preLow + leftSize + 1
leftSize = rootInorderIdx - inLow
把这些起止点都梳理清楚后, 然后就能递归的建树了

int leftSize = rootInorderIdx - inLow;
root->left = build(preorder, inorder, preLow+1, preLow+leftSize, inlow, rootInorderIdx-1);
root->right = build(preorder, inorder, preLow+leftSize+1, preHigh, rootInorderIdx+1, inHigh);

完整代码如下

class Solution {
 private:
  unordered_map<int, int> inorderValue2indx;
 public:
  TreeNode* build(vector<int>& preorder, vector<int>& inorder, int prel, int preh, int inl, int inh) {
    if (prel > preh) {
      return nullptr;
    }
    // 找到根节点
    int rootVal = preorder[prel];
    int rootInorderIdx = inorderValue2indx[rootVal];
    TreeNode* root = new TreeNode(rootVal);
    int leftSize = rootInorderIdx - inl;
    root->left = build(preorder, inorder, prel + 1, prel + leftSize, inl, rootInorderIdx - 1);
    root->right = build(preorder, inorder, prel + leftSize + 1, preh, rootInorderIdx + 1, inh);
    return root;
  }
  TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    for (int i = 0; i < inorder.size(); ++i) {
      inorderValue2indx[inorder[i]] = i;
    }
    return build(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size()-1);
  }
};

这里有个优化点, 因为数组中不存在重复元素, 那么可以提前把中序遍历的根节点对应的index 提前保存在 hash 表中, 找 index 的时间复杂度降低为 O(1)

class Solution {
 private:
  // 记录下来中序遍历中每个值对应的index
  unordered_map<int, int> inorderValue2indx;
 public:
  TreeNode* build(vector<int>& preorder, vector<int>& inorder, int prel, int preh, int inl, int inh) {
    if (prel > preh) {
      return nullptr;
    }
    // 找到根节点
    int rootVal = preorder[prel];
    int rootInorderIdx = inorderValue2indx[rootVal];
    TreeNode* root = new TreeNode(rootVal);
    int leftSize = rootInorderIdx - inl;
    root->left = build(preorder, inorder, prel + 1, prel + leftSize, inl, rootInorderIdx - 1);
    root->right = build(preorder, inorder, prel + leftSize + 1, preh, rootInorderIdx + 1, inh);
    return root;
  }
  TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    for (int i = 0; i < inorder.size(); ++i) {
      inorderValue2indx[inorder[i]] = i;
    }
    return build(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size()-1);
  }
};

106.从中序与后序遍历序列构造二叉树

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

示例 1: 输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3] 输出:[3,9,20,null,null,15,7]

示例 2: 输入:inorder = [-1], postorder = [-1] 输出:[-1]

分析:
1.和前面的方法同理, 先拿一个例子看

      1
     / \
   2     3
  / \   / \ 
 5   4 8   9 
    / \
   6   7

postorder
[5,6,7,4,2],[8,9,3],1
inorder
[5,2,6,4,7],1,[8,3,9]
根节点把 inorder 可以划分成两半, 然后继续递归建立树, 每次建立树在后序数组里面维护起点和终点, 在中序里面也要维护起点和终点
所以思路是先找根节点=>左右子树递归建树, 要写对每个 index 是怎么正确递归的, 每次拿着上面的例子去想 index 怎么构建

TreeNode* build(vector<int>& inorder, vector<int>& postorder, int inLow, int inHigh, int postLow, int postHigh) {
  if (postLow > postHigh) {
    return nullptr;
  }
  // 找到根节点
  int rootVal = postorder[postHigh];
  int rootInorderIdx = inorderIdx[rootVal]; 
  TreeNode* root = newTreeNode(rootVal);
  int leftSize = rootInorderIdx - inLow;
  root->left = build(inorder, postorder, inLow, rootInorderIdx-1, postLow, postLow+leftSize-1);
  root->right = build(inorder, postorder, rootInorderIdx+1, postHigh, postLow+leftSize, postHigh-1);
  return root;
}

完整解法如下

class Solution {
 private:
  unordered_map<int, int> inorderRootIdx;
 public:
  TreeNode* build(vector<int>& inorder, vector<int>& postorder, int inLow, int inHigh, int postLow, int postHigh) {
    if (postLow > postHigh) {
      return nullptr;
    }
    int rootVal = postorder[postHigh];
    int rootInorderIdx = inorderRootIdx[rootVal];
    TreeNode* root = new TreeNode(rootVal);
    int leftSize = rootInorderIdx - inLow;
    root->left = build(inorder, postorder, inLow, rootInorderIdx-1, postLow, postLow+leftSize-1) ;
    root->right = build(inorder, postorder, rootInorderIdx+1, inHigh, postLow+leftSize, postHigh-1) ;
    return root;
  }
  TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
    if (inorder.empty() || postorder.empty()) {
      return nullptr;
    }
    for (int i = 0; i < inorder.size(); ++i) {
      inorderRootIdx[inorder[i]] = i;
    }
    return build(inorder, postorder, 0, inorder.size()-1, 0, postorder.size()-1);
  }
};

889.根据前序和后序遍历构造二叉树

给定两个整数数组,preorder 和 postorder ,其中 preorder 是一个具有 无重复 值的二叉树的前序遍历,postorder 是同一棵树的后序遍历,重构并返回二叉树。
如果存在多个答案,您可以返回其中 任何 一个。

示例 1: 输入:preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1] 输出:[1,2,3,4,5,6,7]

示例 2: 输入: preorder = [1], postorder = [1] 输出: [1]

分析:
1.[前序和后序构建二叉树]和 [前序中序构造二叉树]/[后序中序构造二叉树]有个本质区别, [前序和后序构建二叉树]这种构建无法确定唯一的二叉树, 而是有多种可能的结果
2.造成这种多重结果的原因是什么呢? 通过前序和和后序能找到更节点, 然后再看中序, 能唯一确定左右子树, 那么这棵树就能递归的完全定下来
3.我们没有中序的时候, 其实每次拿到根节点, 但是[左右子树确定不了]
4.举个前序和后序构造二叉树的多重结果的例子

preorder=[1,2,3]
postorder=[3,2,1]

5.递归的做法如下, 仍然是先找根节点, 然后构造左右子树的做法; 这里用一种比较自然的一类构造方法: 拿前序的根节点之后的第一个节点作为左子树的根节点
preorder
[root][[left的root][][]][[right的root][][]]
postorder
[[][][left的root]][[][][right的root]][root]

我们再去关注左子树的根节点, 在后序里面是什么位置

class Solution {
 private:
  unordered_map<int, int> postIdx;
 public:
  TreeNode* build(vector<int>& preorder, vector<int>& postorder, int preLow, int preHigh, int postLow, int postHigh) {
    if (preLow > preHigh) {
      return nullptr;
    }
    int rootVal = preorder[preLow];
    // 这里有个区别, preorder区间长度为1的时候, 需要直接返回根节点, 因为再也没有preLow+1的后续左子树根节点了
    if (preLow == preHigh) {
      return new TreeNode(rootVal);
    }
    int leftRootVal = preorder[preLow+1];
    int rootPostIdx = postIdx[leftRootVal];
    TreeNode* root = new TreeNode(rootVal);
    int leftSize = rootPostIdx - postLow + 1;
    root->left = build(preorder, postorder, preLow+1, preLow+leftSize, postLow, rootPostIdx);
    root->right = build(preorder, postorder, preLow+leftSize+1, preHigh, rootPostIdx+1, postHigh-1);
    return root;
  }
  TreeNode* constructFromPrePost(vector<int>& preorder, vector<int>& postorder) {
    if (preorder.empty() || postorder.empty()) {
      return nullptr;
    }
    for (int i = 0; i < postorder.size(); ++i) {
      postIdx[postorder[i]] = i;
    }
    return build(preorder, postorder, 0, preorder.size()-1, 0, postorder.size()-1);
  }
};

这里我们默认设置这个节点必须是左孩子节点, 实际上构造的时候也能构造成空指针的形式

int leftRootVal = preorder[preLow+1];

转载请注明来源, from goldandrabbit.github.io

💰

×

Help us with donation