前序
字节秋招一面(提前批,商业化技术,base深圳)和美团秋招(base上海),
都出现了差不多的算法题:根据数组形式的二叉树的前序序列和中序序列,假设树种没有重复元素,现要求还原该二叉树,并返回该二叉树的层次序列、后序序列
。
当时手撕算法,是以
牛客ACM模式
,要求自己建立数据结构,传入数组,实现算法。
但只是懂的根据
前序序列和中序序列
来还原二叉树
还是不够的,其他的组合情况也要掌握,在后文一并解决这个。
还原二叉树
4种遍历方式
先来确定二叉树
的4种遍历
方式:
层次序列
/层次遍历
:- 访问根节点
- 从上到下、从左到右,一次遍历每个节点
前序序列
/前序遍历
:- 访问根节点
- 前序遍历左子树
- 前序遍历右子树
中序序列
/中序遍历
:- 中序遍历左子树
- 访问根节点
- 中序遍历右子树
后序序列
/后序遍历
:- 后序遍历左子树
- 后序遍历右子树
- 访问根节点
数据结构
class TreeNode
import java.util.LinkedList; import java.util.Queue; public class TreeNode { int value; TreeNode leftChild; TreeNode rightChild; TreeNode() {} TreeNode(int value) { this.value = value; } TreeNode(int value, TreeNode leftChild, TreeNode rightChild) { this.value = value; this.leftChild = leftChild; this.leftChild = rightChild; } /** * 层序遍历 */ public String readLevel() { Queue<TreeNode> queue = new LinkedList<>(); StringBuilder result = new StringBuilder(); queue.offer(this); while (!queue.isEmpty()) { TreeNode curNode = queue.poll(); result.append(curNode.value + " "); if (curNode.leftChild != null) { queue.offer(curNode.leftChild); } if (curNode.rightChild != null) { queue.offer(curNode.rightChild); } } return result.toString(); } /** * 前序遍历 */ public String readPre() { StringBuilder result = new StringBuilder(); result.append(value + " "); //前序遍历 if (leftChild != null) { result.append(leftChild.readPre()); } if (rightChild != null) { result.append(rightChild.readPre()); } return result.toString(); } /** * 中序遍历 */ public String readMid() { StringBuilder result = new StringBuilder(); if (leftChild != null) { result.append(leftChild.readMid()); } result.append(value + " "); //中序遍历 if (rightChild != null) { result.append(rightChild.readMid()); } return result.toString(); } /** * 后序遍历 */ public String readEnd() { StringBuilder result = new StringBuilder(); if (leftChild != null) { result.append(leftChild.readEnd()); } if (rightChild != null) { result.append(rightChild.readEnd()); } result.append(value + " "); //后序遍历 return result.toString(); } @Override public boolean equals(Object parm) { if (parm == null || !(parm instanceof TreeNode)) { return false; } TreeNode obj = (TreeNode) parm; if (this.value == obj.value) { // if (this.value == obj.value || this.value.equals(obj.value)) { if ((this.leftChild == null && obj.leftChild == null) || this.leftChild.equals(obj.leftChild)) { if ((this.rightChild == null && obj.rightChild == null) || this.rightChild.equals(obj.rightChild)) { return true; } } } return false; } }
3种组合的算法
1. 前序序列+中序序列
前序序列的第一个元素
必定是根结点
;- 在
中序序列
中找根节点
,确定根结点
的左子树
、右子树
的中序序列
; - 在
前序序列
中确定根结点
的左子树
、右子树
的前序序列
; - 由
左子树
的前序序列
和中序序列
建立左子树
; - 由
右子树
的前序序列
和中序序列
建立右子树
。
/** * 已知 前序序列+中序序列 * * @param preOrder 前序序列 * @param midOrder 中序序列 * @return root 根节点 */ public static TreeNode reConstruct_PreMid(int[] preOrder, int[] midOrder) { if (preOrder.length == 0 || midOrder.length == 0) { return null; } // preOrder[0] 必定是root根节点 TreeNode root = new TreeNode(preOrder[0]); for (int i = 0; i < midOrder.length; i++) { if (preOrder[0] == midOrder[i]) { root.leftChild = reConstruct_PreMid(Arrays.copyOfRange(preOrder, 1, i + 1), Arrays.copyOfRange(midOrder, 0, i)); root.rightChild = reConstruct_PreMid(Arrays.copyOfRange(preOrder, i + 1, preOrder.length), Arrays.copyOfRange(midOrder, i + 1, midOrder.length)); } } return root; }
2. 中序序列+后序序列
后序序列的最后一个元素
必定是根结点
;- 在
中序序列
中找根节点
,确定根结点
的左子树
、右子树
的中序序列
; - 在
后序序列
中确定根结点
的左子树
、右子树
的后序序列
; - 由
左子树
的中序序列
和后序序列
建立左子树
; - 由
右子树
的中序序列
和后序序列
建立右子树
。
/** * 已知 中序序列+后序序列 * * @param midOrder * @param endOrder * @return root 根节点 */ public static TreeNode reConstruct_MidEnd(int[] midOrder, int[] endOrder) { if (midOrder.length == 0 || endOrder.length == 0) { return null; } // endOrder[endOrder.length-1] 必定是root根节点 TreeNode root = new TreeNode(endOrder[endOrder.length - 1]); for (int i = 0; i < midOrder.length; i++) { if (endOrder[endOrder.length - 1] == midOrder[i]) { root.leftChild = reConstruct_MidEnd(Arrays.copyOfRange(midOrder, 0, i), Arrays.copyOfRange(endOrder, 0, i)); root.rightChild = reConstruct_MidEnd(Arrays.copyOfRange(midOrder, i + 1, midOrder.length), Arrays.copyOfRange(endOrder, i, endOrder.length - 1)); } } return root; }
3. 层序序列+中序序列
这种组合情况比较复杂,需要考虑节点所在的层数,相对应的算法也比较复杂,下面的算法是参考了GitHub上看到的。
层序序列的第一个元素
必定是根结点
;- 根据
根节点
在中序序列
中,划分左子树
的中序序列
、右子树
的中序序列
; - 在
层序序列
中获取
其左子树
、右子树
的层序序列
; - 由
左子树
的层序序列
和中序序列
建立左子树
; - 由
右子树
的层序序列
和中序序列
建立右子树
。
/** * 已知 层序序列+中序序列 * * @param levelOrder * @param midOrder * @param midBegin 中序序列的起始位置 * @param midEnd 中序序列的中止位置 * @return root 根节点 */ public static TreeNode buildTreeByMidLevel(int[] levelOrder, int[] midOrder, int midBegin, int midEnd) { if (levelOrder.length == 0 || midOrder.length == 0) { return null; } // levelOrder[0] 必定是root根节点 TreeNode root = new TreeNode(levelOrder[0]); // rootLocation:(子)序列中根节点的位置 int rootLocation = -1; for (int i = midBegin; i <= midEnd; i++) { if (midOrder[i] == levelOrder[0]) { rootLocation = i; break; } } // 划分左右子树 // levelOrder.length会影响划分 if (levelOrder.length >= 2) { if (isLeft(midOrder, levelOrder[0], levelOrder[1])) { // 左子树 root.leftChild = buildTreeByMidLevel(getLevelStr(midOrder, midBegin, rootLocation - 1, levelOrder), midOrder, midBegin, rootLocation - 1); // 处理左子树为空的情况,注意levelOrder.length需>=3 if (levelOrder.length >= 3 && !isLeft(midOrder, levelOrder[0], levelOrder[2])) { root.rightChild = buildTreeByMidLevel(getLevelStr(midOrder, rootLocation + 1, midEnd, levelOrder), midOrder, rootLocation + 1, midEnd); } } else { // 右子树 root.rightChild = buildTreeByMidLevel(getLevelStr(midOrder, rootLocation + 1, midEnd, levelOrder), midOrder, rootLocation + 1, midEnd); } } return root; } /** * 区分左右子树 * 在中序序列[]中,若孩子节点在根节点左边,则返回true,否则返回false * * @param order * @param root * @param child * @return isLeft/isRight */ public static boolean isLeft(int[] order, int root, int child) { boolean findVal = false; for (int temp : order) { if (temp == child) { findVal = true; } else if (temp == root) { return findVal; } } return false; } /** * 获取左右子树各自在层序序列中的子序列 * 将中序序列中midBegin与MidEnd之间的节点依次从levelOrder中提取出来,保持levelOrder中的字符顺序不变 * 比如,右子树的中序序列[15,20,7]在层序序列中是[20,15,7] * * @param midOrder * @param midBegin * @param midEnd * @param levelOrder * @return result[] 子树在层序序列中的子序列 */ public static int[] getLevelStr(int[] midOrder, int midBegin, int midEnd, int[] levelOrder) { int[] result = new int[midEnd - midBegin + 1]; int curLoc = 0; for (int i = 0; i < levelOrder.length; i++) { if (contains(midOrder, levelOrder[i], midBegin, midEnd)) { result[curLoc++] = levelOrder[i]; } } return result; } /** * 判断层序序列中的节点是否在中序序列中 * 在中序序列[]中的begin和end位置之间(包括begin和end)含有字符target,则返回true * * @param midOrder * @param target * @param midBegin * @param midEnd * @return false/true */ public static boolean contains(int[] midOrder, int target, int midBegin, int midEnd) { for (int i = midBegin; i <= midEnd; i++) { if (midOrder[i] == target) { return true; } } return false; }
其他情况
其他的遍历组合均不能还原出二叉树的形状,因为无法确认其左右孩子。
例如:前序序列
+后序序列
,前序序列
和后序序列
在本质上
都是将父节点与子结点进行分离
,但并没有指明左子树和右子树的能力
,因此这两个序列只能明确根节点、父子关系
,而不能确定一个二叉树。
参考资料
完整代码
import java.util.Arrays; /** * Solution */ public class Solution { public static void main(String[] args) { int[] levelOrder = new int[] { 3, 9, 20, 15, 7 }; int[] preOrder = new int[] { 3, 9, 20, 15, 7 }; int[] midOrder = new int[] { 9, 3, 15, 20, 7 }; int[] endOrder = new int[] { 9, 15, 7, 20, 3 }; System.out.println("==已知前序序列+中序序列=="); System.out.println("层序遍历: " + reConstruct_PreMid(preOrder, midOrder).readLevel()); // System.out.println("前序遍历: " + reConstruct_PreMid(preOrder, midOrder).readPre()); // System.out.println("中序遍历: " + reConstruct_PreMid(preOrder, midOrder).readMid()); System.out.println("后序遍历: " + reConstruct_PreMid(preOrder, midOrder).readEnd()); System.out.println(); System.out.println("==已知中序序列+后序序列=="); System.out.println("层序遍历: " + reConstruct_MidEnd(midOrder, endOrder).readLevel()); System.out.println("前序遍历: " + reConstruct_MidEnd(midOrder, endOrder).readPre()); // System.out.println("中序遍历: " + reConstruct_MidEnd(midOrder, endOrder).readMid()); // System.out.println("后序遍历: " + reConstruct_MidEnd(midOrder, endOrder).readEnd()); System.out.println(); System.out.println("==已知层序序列+中序序列=="); // System.out.println("层序遍历: " + buildTreeByMidLevel(levelOrder, midOrder, 0, midOrder.length-1).readLevel()); System.out.println("前序遍历: " + buildTreeByMidLevel(levelOrder, midOrder, 0, midOrder.length-1).readPre()); // System.out.println("中序遍历: " + buildTreeByMidLevel(levelOrder, midOrder, 0, midOrder.length-1).readMid()); System.out.println("后序遍历: " + buildTreeByMidLevel(levelOrder, midOrder, 0, midOrder.length-1).readEnd()); System.out.println(); } /** * 已知 前序序列+中序序列 * * @param preOrder 前序序列 * @param midOrder 中序序列 * @return root 根节点 */ public static TreeNode reConstruct_PreMid(int[] preOrder, int[] midOrder) { if (preOrder.length == 0 || midOrder.length == 0) { return null; } // preOrder[0] 必定是root根节点 TreeNode root = new TreeNode(preOrder[0]); for (int i = 0; i < midOrder.length; i++) { if (preOrder[0] == midOrder[i]) { root.leftChild = reConstruct_PreMid(Arrays.copyOfRange(preOrder, 1, i + 1), Arrays.copyOfRange(midOrder, 0, i)); root.rightChild = reConstruct_PreMid(Arrays.copyOfRange(preOrder, i + 1, preOrder.length), Arrays.copyOfRange(midOrder, i + 1, midOrder.length)); } } return root; } /** * 已知 中序序列+后序序列 * * @param midOrder * @param endOrder * @return root 根节点 */ public static TreeNode reConstruct_MidEnd(int[] midOrder, int[] endOrder) { if (midOrder.length == 0 || endOrder.length == 0) { return null; } // endOrder[endOrder.length-1] 必定是root根节点 TreeNode root = new TreeNode(endOrder[endOrder.length - 1]); for (int i = 0; i < midOrder.length; i++) { if (endOrder[endOrder.length - 1] == midOrder[i]) { root.leftChild = reConstruct_MidEnd(Arrays.copyOfRange(midOrder, 0, i), Arrays.copyOfRange(endOrder, 0, i)); root.rightChild = reConstruct_MidEnd(Arrays.copyOfRange(midOrder, i + 1, midOrder.length), Arrays.copyOfRange(endOrder, i, endOrder.length - 1)); } } return root; } /** * 已知 层序序列+中序序列 * * @param levelOrder * @param midOrder * @param midBegin 中序序列的起始位置 * @param midEnd 中序序列的中止位置 * @return root 根节点 */ public static TreeNode buildTreeByMidLevel(int[] levelOrder, int[] midOrder, int midBegin, int midEnd) { if (levelOrder.length == 0 || midOrder.length == 0) { return null; } // levelOrder[0] 必定是root根节点 TreeNode root = new TreeNode(levelOrder[0]); // rootLocation:(子)序列中根节点的位置 int rootLocation = -1; for (int i = midBegin; i <= midEnd; i++) { if (midOrder[i] == levelOrder[0]) { rootLocation = i; break; } } // 划分左右子树 // levelOrder.length会影响划分 if (levelOrder.length >= 2) { if (isLeft(midOrder, levelOrder[0], levelOrder[1])) { // 左子树 root.leftChild = buildTreeByMidLevel(getLevelStr(midOrder, midBegin, rootLocation - 1, levelOrder), midOrder, midBegin, rootLocation - 1); // 处理左子树为空的情况,注意levelOrder.length需>=3 if (levelOrder.length >= 3 && !isLeft(midOrder, levelOrder[0], levelOrder[2])) { root.rightChild = buildTreeByMidLevel(getLevelStr(midOrder, rootLocation + 1, midEnd, levelOrder), midOrder, rootLocation + 1, midEnd); } } else { // 右子树 root.rightChild = buildTreeByMidLevel(getLevelStr(midOrder, rootLocation + 1, midEnd, levelOrder), midOrder, rootLocation + 1, midEnd); } } return root; } /** * 区分左右子树 * 在中序序列[]中,若孩子节点在根节点左边,则返回true,否则返回false * * @param order * @param root * @param child * @return isLeft/isRight */ public static boolean isLeft(int[] order, int root, int child) { boolean findVal = false; for (int temp : order) { if (temp == child) { findVal = true; } else if (temp == root) { return findVal; } } return false; } /** * 获取左右子树各自在层序序列中的子序列 * 将中序序列中midBegin与MidEnd之间的节点依次从levelOrder中提取出来,保持levelOrder中的字符顺序不变 * 比如,右子树的中序序列[15,20,7]在层序序列中是[20,15,7] * * @param midOrder * @param midBegin * @param midEnd * @param levelOrder * @return result[] 子树在层序序列中的子序列 */ public static int[] getLevelStr(int[] midOrder, int midBegin, int midEnd, int[] levelOrder) { int[] result = new int[midEnd - midBegin + 1]; int curLoc = 0; for (int i = 0; i < levelOrder.length; i++) { if (contains(midOrder, levelOrder[i], midBegin, midEnd)) { result[curLoc++] = levelOrder[i]; } } return result; } /** * 判断层序序列中的节点是否在中序序列中 * 在中序序列[]中的begin和end位置之间(包括begin和end)含有字符target,则返回true * * @param midOrder * @param target * @param midBegin * @param midEnd * @return false/true */ public static boolean contains(int[] midOrder, int target, int midBegin, int midEnd) { for (int i = midBegin; i <= midEnd; i++) { if (midOrder[i] == target) { return true; } } return false; } }
全部评论
(0) 回帖