【算法17】-【从中序与后序遍历序列构造二叉树】
题目:根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
方法一:递归
首先解决这道题我们需要明确给定一棵二叉树,我们是如何对其进行中序遍历与后序遍历的:
1)中序遍历的顺序是每次遍历左孩子,再遍历根节点,最后遍历右孩子。
2)后序遍历的顺序是每次遍历左孩子,再遍历右孩子,最后遍历根节点。
思路:我们可以发现后序遍历的数组最后一个元素代表的即为根节点。知道这个性质后,我们可以利用已知的根节点信息在中序遍历的数组中找到根节点所在的下标,然后根据其将中序遍历的数组分成左右两部分,左边部分即左子树,右边部分为右子树,针对每个部分可以用同样的方法继续递归下去构造。
1)中序遍历的顺序是每次遍历左孩子,再遍历根节点,最后遍历右孩子。
2)后序遍历的顺序是每次遍历左孩子,再遍历右孩子,最后遍历根节点。
思路:我们可以发现后序遍历的数组最后一个元素代表的即为根节点。知道这个性质后,我们可以利用已知的根节点信息在中序遍历的数组中找到根节点所在的下标,然后根据其将中序遍历的数组分成左右两部分,左边部分即左子树,右边部分为右子树,针对每个部分可以用同样的方法继续递归下去构造。
class Solution { int post_idx; int[] postorder; int[] inorder; Map<Integer, Integer> idx_map = new HashMap<Integer, Integer>(); public TreeNode helper(int in_left, int in_right) { // 如果这里没有节点构造二叉树了,就结束 if (in_left > in_right) { return null; } // 选择 post_idx 位置的元素作为当前子树根节点 int root_val = postorder[post_idx]; TreeNode root = new TreeNode(root_val); // 根据 root 所在位置分成左右两棵子树 int index = idx_map.get(root_val); // 下标减一 post_idx--; // 构造右子树 root.right = helper(index + 1, in_right); // 构造左子树 root.left = helper(in_left, index - 1); return root; } public TreeNode buildTree(int[] inorder, int[] postorder) { this.postorder = postorder; this.inorder = inorder; // 从后序遍历的最后一个元素开始 post_idx = postorder.length - 1; // 建立(元素,下标)键值对的哈希表 int idx = 0; for (Integer val : inorder) { idx_map.put(val, idx++); } return helper(0, inorder.length - 1); } }
方法二:迭代(有点复杂)
迭代法是一种非常巧妙的实现方法。迭代法的实现基于以下两点发现。
1)如果将中序遍历反序,则得到反向的中序遍历,即每次遍历右孩子,再遍历根节点,最后遍历左孩子。
2)如果将后序遍历反序,则得到反向的前序遍历,即每次遍历根节点,再遍历右孩子,最后遍历左孩子。
「反向」的意思是交换遍历左孩子和右孩子的顺序,即反向的遍历中,右孩子在左孩子之前被遍历。
因此可以使用和「105. 从前序与中序遍历序列构造二叉树」的迭代方法类似的方法构造二叉树。
对于后序遍历中的任意两个连续节点 u 和 v(在后序遍历中,u 在 v 的前面),根据后序遍历的流程,我们可以知道 u 和 v 只有两种可能的关系:
1)u 是 v 的右儿子。这是因为在遍历到 u 之后,下一个遍历的节点就是 u 的双亲节点,即 v;
2)v 没有右儿子,并且 u 是 v 的某个祖先节点(或者 v 本身)的左儿子。如果 v 没有右儿子,那么上一个遍历的节点就是 v 的左儿子。如果 v 没有左儿子,则从 v 开始向上遍历 v 的祖先节点,直到遇到一个有左儿子(且 v 不在它的左儿子的子树中)的节点 va,那么 u 就是 va的左儿子。
1)如果将中序遍历反序,则得到反向的中序遍历,即每次遍历右孩子,再遍历根节点,最后遍历左孩子。
2)如果将后序遍历反序,则得到反向的前序遍历,即每次遍历根节点,再遍历右孩子,最后遍历左孩子。
「反向」的意思是交换遍历左孩子和右孩子的顺序,即反向的遍历中,右孩子在左孩子之前被遍历。
因此可以使用和「105. 从前序与中序遍历序列构造二叉树」的迭代方法类似的方法构造二叉树。
对于后序遍历中的任意两个连续节点 u 和 v(在后序遍历中,u 在 v 的前面),根据后序遍历的流程,我们可以知道 u 和 v 只有两种可能的关系:
1)u 是 v 的右儿子。这是因为在遍历到 u 之后,下一个遍历的节点就是 u 的双亲节点,即 v;
2)v 没有右儿子,并且 u 是 v 的某个祖先节点(或者 v 本身)的左儿子。如果 v 没有右儿子,那么上一个遍历的节点就是 v 的左儿子。如果 v 没有左儿子,则从 v 开始向上遍历 v 的祖先节点,直到遇到一个有左儿子(且 v 不在它的左儿子的子树中)的节点 va,那么 u 就是 va的左儿子。
class Solution { public TreeNode buildTree(int[] inorder, int[] postorder) { if (postorder == null || postorder.length == 0) { return null; } TreeNode root = new TreeNode(postorder[postorder.length - 1]); Deque<TreeNode> stack = new LinkedList<TreeNode>(); stack.push(root); int inorderIndex = inorder.length - 1; for (int i = postorder.length - 2; i >= 0; i--) { int postorderVal = postorder[i]; TreeNode node = stack.peek(); if (node.val != inorder[inorderIndex]) { node.right = new TreeNode(postorderVal); stack.push(node.right); } else { while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) { node = stack.pop(); inorderIndex--; } node.left = new TreeNode(postorderVal); stack.push(node.left); } } return root; } }