面试大总结之二:Java搞定面试中的二叉树题目
package BinaryTreeSummary; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Stack; /** * http://blog.csdn.net/luckyxiaoqiang/article/details/7518888 轻松搞定面试中的二叉树题目 * http://www.cnblogs.com/Jax/archive/2009/12/28/1633691.html 算法大全(3) 二叉树 * * TODO: 一定要能熟练地写出所有问题的递归和非递归做法! * * 1. 求二叉树中的节点个数: getNodeNumRec(递归),getNodeNum(迭代) * 2. 求二叉树的深度: getDepthRec(递归),getDepth * 3. 前序遍历,中序遍历,后序遍历: preorderTraversalRec, preorderTraversal, inorderTraversalRec, postorderTraversalRec * (https://en.wikipedia.org/wiki/Tree_traversal#Pre-order_2) * 4.分层遍历二叉树(按层次从上往下,从左往右): levelTraversal, levelTraversalRec(递归解法!) * 5. 将二叉查找树变为有序的双向链表: convertBST2DLLRec, convertBST2DLL * 6. 求二叉树第K层的节点个数:getNodeNumKthLevelRec, getNodeNumKthLevel * 7. 求二叉树中叶子节点的个数:getNodeNumLeafRec, getNodeNumLeaf * 8. 判断两棵二叉树是否相同的树:isSameRec, isSame * 9. 判断二叉树是不是平衡二叉树:isAVLRec * 10. 求二叉树的镜像(破坏和不破坏原来的树两种情况):mirrorRec, mirrorCopyRec * 10.1 判断两个树是否互相镜像:isMirrorRec * 11. 求二叉树中两个节点的最低公共祖先节点:getLastCommonParent, getLastCommonParentRec, getLastCommonParentRec2 * 12. 求二叉树中节点的最大距离:getMaxDistanceRec * 13. 由前序遍历序列和中序遍历序列重建二叉树:rebuildBinaryTreeRec * 14.判断二叉树是不是完全二叉树:isCompleteBinaryTree, isCompleteBinaryTreeRec * */ public class Demo { /* 1 / \ 2 3 / \ \ 4 5 6 */ public static void main(String[] args) { TreeNode r1 = new TreeNode(1); TreeNode r2 = new TreeNode(2); TreeNode r3 = new TreeNode(3); TreeNode r4 = new TreeNode(4); TreeNode r5 = new TreeNode(5); TreeNode r6 = new TreeNode(6); r1.left = r2; r1.right = r3; r2.left = r4; r2.right = r5; r3.right = r6; // System.out.println(getNodeNumRec(r1)); // System.out.println(getNodeNum(r1)); // System.out.println(getDepthRec(r1)); // System.out.println(getDepth(r1)); // preorderTraversalRec(r1); // System.out.println(); // preorderTraversal(r1); // System.out.println(); // inorderTraversalRec(r1); // System.out.println(); // inorderTraversal(r1); // System.out.println(); // postorderTraversalRec(r1); // System.out.println(); // postorderTraversal(r1); // System.out.println(); // levelTraversal(r1); // System.out.println(); // levelTraversalRec(r1); // System.out.println(); // TreeNode tmp = convertBSTRec(r1); // while(true){ // if(tmp == null){ // break; // } // System.out.print(tmp.val + " "); // if(tmp.right == null){ // break; // } // tmp = tmp.right; // } // System.out.println(); // while(true){ // if(tmp == null){ // break; // } // System.out.print(tmp.val + " "); // if(tmp.left == null){ // break; // } // tmp = tmp.left; // } // TreeNode tmp = convertBST2DLL(r1); // while(true){ // if(tmp == null){ // break; // } // System.out.print(tmp.val + " "); // if(tmp.right == null){ // break; // } // tmp = tmp.right; // } // System.out.println(getNodeNumKthLevelRec(r1, 2)); // System.out.println(getNodeNumKthLevel(r1, 2)); // System.out.println(getNodeNumLeafRec(r1)); // System.out.println(getNodeNumLeaf(r1)); // System.out.println(isSame(r1, r1)); // inorderTraversal(r1); // System.out.println(); // mirror(r1); // TreeNode mirrorRoot = mirrorCopy(r1); // inorderTraversal(mirrorRoot); System.out.println(isCompleteBinaryTree(r1)); System.out.println(isCompleteBinaryTreeRec(r1)); } private static class TreeNode { int val; TreeNode left; TreeNode right; public TreeNode(int val) { this.val = val; } } /** * 求二叉树中的节点个数递归解法: O(n) * (1)如果二叉树为空,节点个数为0 * (2)如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + * 右子树节点个数 + 1 */ public static int getNodeNumRec(TreeNode root) { if (root == null) { return 0; } else { return getNodeNumRec(root.left) + getNodeNumRec(root.right) + 1; } } /** * 求二叉树中的节点个数迭代解法O(n):基本思想同LevelOrderTraversal, * 即用一个Queue,在Java里面可以用LinkedList来模拟 */ public static int getNodeNum(TreeNode root) { if(root == null){ return 0; } int count = 1; Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); while(!queue.isEmpty()){ TreeNode cur = queue.remove(); // 从队头位置移除 if(cur.left != null){ // 如果有左孩子,加到队尾 queue.add(cur.left); count++; } if(cur.right != null){ // 如果有右孩子,加到队尾 queue.add(cur.right); count++; } } return count; } /** * 求二叉树的深度(高度) 递归解法: O(n) * (1)如果二叉树为空,二叉树的深度为0 * (2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1 */ public static int getDepthRec(TreeNode root) { if (root == null) { return 0; } int leftDepth = getDepthRec(root.left); int rightDepth = getDepthRec(root.right); return Math.max(leftDepth, rightDepth) + 1; } /** * 求二叉树的深度(高度) 迭代解法: O(n) * 基本思想同LevelOrderTraversal,还是用一个Queue */ public static int getDepth(TreeNode root) { if(root == null){ return 0; } int depth = 0; // 深度 int currentLevelNodes = 1; // 当前Level,node的数量 int nextLevelNodes = 0; // 下一层Level,node的数量 LinkedList<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); while( !queue.isEmpty() ){ TreeNode cur = queue.remove(); // 从队头位置移除 currentLevelNodes--; // 减少当前Level node的数量 if(cur.left != null){ // 如果有左孩子,加到队尾 queue.add(cur.left); nextLevelNodes++; // 并增加下一层Level node的数量 } if(cur.right != null){ // 如果有右孩子,加到队尾 queue.add(cur.right); nextLevelNodes++; } if(currentLevelNodes == 0){ // 说明已经遍历完当前层的所有节点 depth++; // 增加高度 currentLevelNodes = nextLevelNodes; // 初始化下一层的遍历 nextLevelNodes = 0; } } return depth; } /** * 前序遍历,中序遍历,后序遍历 前序遍历递归解法: * (1)如果二叉树为空,空操作 * (2)如果二叉树不为空,访问根节点,前序遍历左子树,前序遍历右子树 */ public static void preorderTraversalRec(TreeNode root) { if (root == null) { return; } System.out.print(root.val + " "); preorderTraversalRec(root.left); preorderTraversalRec(root.right); } /** * 前序遍历迭代解法:用一个辅助stack,总是把右孩子放进栈 * http://www.youtube.com/watch?v=uPTCbdHSFg4 */ public static void preorderTraversal(TreeNode root) { if(root == null){ return; } Stack<TreeNode> stack = new Stack<TreeNode>(); // 辅助stack stack.push(root); while( !stack.isEmpty() ){ TreeNode cur = stack.pop(); // 出栈栈顶元素 System.out.print(cur.val + " "); // 关键点:要先压入右孩子,再压入左孩子,这样在出栈时会先打印左孩子再打印右孩子 if(cur.right != null){ stack.push(cur.right); } if(cur.left != null){ stack.push(cur.left); } } } /** * 中序遍历递归解法 * (1)如果二叉树为空,空操作。 * (2)如果二叉树不为空,中序遍历左子树,访问根节点,中序遍历右子树 */ public static void inorderTraversalRec(TreeNode root) { if (root == null) { return; } inorderTraversalRec(root.left); System.out.print(root.val + " "); inorderTraversalRec(root.right); } /** * 中序遍历迭代解法 ,用栈先把根节点的所有左孩子都添加到栈内, * 然后输出栈顶元素,再处理栈顶元素的右子树 * http://www.youtube.com/watch?v=50v1sJkjxoc * * 还有一种方法能不用递归和栈,基于线索二叉树的方法,较麻烦以后补上 * http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion-and-without-stack/ */ public static void inorderTraversal(TreeNode root){ if(root == null){ return; } Stack<TreeNode> stack = new Stack<TreeNode>(); TreeNode cur = root; while( true ){ while(cur != null){ // 先添加一个非空节点所有的左孩子到栈 stack.push(cur); cur = cur.left; } if(stack.isEmpty()){ break; } // 因为此时已经没有左孩子了,所以输出栈顶元素 cur = stack.pop(); System.out.print(cur.val + " "); cur = cur.right; // 准备处理右子树 } } /** * 后序遍历递归解法 * (1)如果二叉树为空,空操作 * (2)如果二叉树不为空,后序遍历左子树,后序遍历右子树,访问根节点 */ public static void postorderTraversalRec(TreeNode root) { if (root == null) { return; } postorderTraversalRec(root.left); postorderTraversalRec(root.right); System.out.print(root.val + " "); } /** * 后序遍历迭代解法 * http://www.youtube.com/watch?v=hv-mJUs5mvU * */ public static void postorderTraversal(TreeNode root) { if (root == null) { return; } Stack<TreeNode> s = new Stack<TreeNode>(); // 第一个stack用于添加node和它的左右孩子 Stack<TreeNode> output = new Stack<TreeNode>();// 第二个stack用于翻转第一个stack输出 s.push(root); while( !s.isEmpty() ){ // 确保所有元素都被翻转转移到第二个stack TreeNode cur = s.pop(); // 把栈顶元素添加到第二个stack output.push(cur); if(cur.left != null){ // 把栈顶元素的左孩子和右孩子分别添加入第一个stack s.push(cur.left); } if(cur.right != null){ s.push(cur.right); } } while( !output.isEmpty() ){ // 遍历输出第二个stack,即为后序遍历 System.out.print(output.pop().val + " "); } } /** * 分层遍历二叉树(按层次从上往下,从左往右)迭代 * 相当于广度优先搜索,使用队列实现。队列初始化,将根节点压入队列。当队列不为空,进行如下操作:弹出一个节点 * ,访问,若左子节点或右子节点不为空,将其压入队列 */ public static void levelTraversal(TreeNode root) { if (root == null) { return; } LinkedList<TreeNode> queue = new LinkedList<TreeNode>(); queue.push(root); while (!queue.isEmpty()) { TreeNode cur = queue.removeFirst(); System.out.print(cur.val + " "); if (cur.left != null) { queue.add(cur.left); } if (cur.right != null) { queue.add(cur.right); } } } /** * 分层遍历二叉树(递归) * 很少有人会用递归去做level traversal * 基本思想是用一个大的ArrayList,里面包含了每一层的ArrayList。 * 大的ArrayList的size和level有关系 * * 这是我目前见到的最好的递归解法! * http://discuss.leetcode.com/questions/49/binary-tree-level-order-traversal#answer-container-2543 */ public static void levelTraversalRec(TreeNode root) { ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>(); dfs(root, 0, ret); System.out.println(ret); } private static void dfs(TreeNode root, int level, ArrayList<ArrayList<Integer>> ret){ if(root == null){ return; } // 添加一个新的ArrayList表示新的一层 if(level >= ret.size()){ ret.add(new ArrayList<Integer>()); } ret.get(level).add(root.val); // 把节点添加到表示那一层的ArrayList里 dfs(root.left, level+1, ret); // 递归处理下一层的左子树和右子树 dfs(root.right, level+1, ret); } /** * 将二叉查找树变为有序的双向链表 要求不能创建新节点,只调整指针。 * 递归解法: * 参考了http://stackoverflow.com/questions/11511898/converting-a-binary-search-tree-to-doubly-linked-list#answer-11530016 * 感觉是最清晰的递归解法,但要注意递归完,root会在链表的中间位置,因此要手动 * 把root移到链表头或链表尾 */ public static TreeNode convertBST2DLLRec(TreeNode root) { root = convertBST2DLLSubRec(root); // root会在链表的中间位置,因此要手动把root移到链表头 while(root.left != null){ root = root.left; } return root; } /** * 递归转换BST为双向链表(DLL) */ public static TreeNode convertBST2DLLSubRec(TreeNode root){ if(root==null || (root.left==null && root.right==null)){ return root; } TreeNode tmp = null; if(root.left != null){ // 处理左子树 tmp = convertBST2DLLSubRec(root.left); while(tmp.right != null){ // 寻找最右节点 tmp = tmp.right; } tmp.right = root; // 把左子树处理后结果和root连接 root.left = tmp; } if(root.right != null){ // 处理右子树 tmp = convertBST2DLLSubRec(root.right); while(tmp.left != null){ // 寻找最左节点 tmp = tmp.left; } tmp.left = root; // 把右子树处理后结果和root连接 root.right = tmp; } return root; } /** * 将二叉查找树变为有序的双向链表 迭代解法 // * 类似inorder traversal的做法 */ public static TreeNode convertBST2DLL(TreeNode root) { if(root == null){ return null; } Stack<TreeNode> stack = new Stack<TreeNode>(); TreeNode cur = root; // 指向当前处理节点 TreeNode old = null; // 指向前一个处理的节点 TreeNode head = null; // 链表头 while( true ){ while(cur != null){ // 先添加一个非空节点所有的左孩子到栈 stack.push(cur); cur = cur.left; } if(stack.isEmpty()){ break; } // 因为此时已经没有左孩子了,所以输出栈顶元素 cur = stack.pop(); if(old != null){ old.right = cur; } if(head == null){ // /第一个节点为双向链表头节点 head = cur; } old = cur; // 更新old cur = cur.right; // 准备处理右子树 } return head; } /** * 求二叉树第K层的节点个数 递归解法: * (1)如果二叉树为空或者k<1返回0 * (2)如果二叉树不为空并且k==1,返回1 * (3)如果二叉树不为空且k>1,返回root左子树中k-1层的节点个数与root右子树k-1层节点个数之和 * * 求以root为根的k层节点数目 等价于 求以root左孩子为根的k-1层(因为少了root那一层)节点数目 加上 * 以root右孩子为根的k-1层(因为少了root那一层)节点数目 * * 所以遇到树,先把它拆成左子树和右子树,把问题降解 * */ public static int getNodeNumKthLevelRec(TreeNode root, int k) { if (root == null || k < 1) { return 0; } if (k == 1) { return 1; } int numLeft = getNodeNumKthLevelRec(root.left, k - 1); // 求root左子树的k-1层节点数 int numRight = getNodeNumKthLevelRec(root.right, k - 1); // 求root右子树的k-1层节点数 return numLeft + numRight; } /** * 求二叉树第K层的节点个数 迭代解法: * 同getDepth的迭代解法 */ public static int getNodeNumKthLevel(TreeNode root, int k){ if(root == null){ return 0; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); int i = 1; int currentLevelNodes = 1; // 当前Level,node的数量 int nextLevelNodes = 0; // 下一层Level,node的数量 while( !queue.isEmpty() && i<k){ TreeNode cur = queue.remove(); // 从队头位置移除 currentLevelNodes--; // 减少当前Level node的数量 if(cur.left != null){ // 如果有左孩子,加到队尾 queue.add(cur.left); nextLevelNodes++; // 并增加下一层Level node的数量 } if(cur.right != null){ // 如果有右孩子,加到队尾 queue.add(cur.right); nextLevelNodes++; } if(currentLevelNodes == 0){ // 说明已经遍历完当前层的所有节点 currentLevelNodes = nextLevelNodes; // 初始化下一层的遍历 nextLevelNodes = 0; i++; // 进入到下一层 } } return currentLevelNodes; } /** * 求二叉树中叶子节点的个数(递归) */ public static int getNodeNumLeafRec(TreeNode root) { // 当root不存在,返回空 if (root == null) { return 0; } // 当为叶子节点时返回1 if (root.left == null && root.right == null) { return 1; } // 把一个树拆成左子树和右子树之和,原理同上一题 return getNodeNumLeafRec(root.left) + getNodeNumLeafRec(root.right); } /** * 求二叉树中叶子节点的个数(迭代) * 还是基于Level order traversal */ public static int getNodeNumLeaf(TreeNode root) { if(root == null){ return 0; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); int leafNodes = 0; // 记录上一个Level,node的数量 while( !queue.isEmpty() ){ TreeNode cur = queue.remove(); // 从队头位置移除 if(cur.left != null){ // 如果有左孩子,加到队尾 queue.add(cur.left); } if(cur.right != null){ // 如果有右孩子,加到队尾 queue.add(cur.right); } if(cur.left==null && cur.right==null){ // 叶子节点 leafNodes++; } } return leafNodes; } /** * 判断两棵二叉树是否相同的树。 * 递归解法: * (1)如果两棵二叉树都为空,返回真 * (2)如果两棵二叉树一棵为空,另一棵不为空,返回假 * (3)如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假 */ public static boolean isSameRec(TreeNode r1, TreeNode r2) { // 如果两棵二叉树都为空,返回真 if (r1 == null && r2 == null) { return true; } // 如果两棵二叉树一棵为空,另一棵不为空,返回假 else if (r1 == null || r2 == null) { return false; } if(r1.val != r2.val){ return false; } boolean leftRes = isSameRec(r1.left, r2.left); // 比较对应左子树 boolean rightRes = isSameRec(r1.right, r2.right); // 比较对应右子树 return leftRes && rightRes; } /** * 判断两棵二叉树是否相同的树(迭代) * 遍历一遍即可,这里用preorder */ public static boolean isSame(TreeNode r1, TreeNode r2) { // 如果两个树都是空树,则返回true if(r1==null && r2==null){ return true; } // 如果有一棵树是空树,另一颗不是,则返回false if(r1==null || r2==null){ return false; } Stack<TreeNode> s1 = new Stack<TreeNode>(); Stack<TreeNode> s2 = new Stack<TreeNode>(); s1.push(r1); s2.push(r2); while(!s1.isEmpty() && !s2.isEmpty()){ TreeNode n1 = s1.pop(); TreeNode n2 = s2.pop(); if(n1==null && n2==null){ continue; }else if(n1!=null && n2!=null && n1.val==n2.val){ s1.push(n1.right); s1.push(n1.left); s2.push(n2.right); s2.push(n2.left); }else{ return false; } } return true; } /** * 判断二叉树是不是平衡二叉树 递归解法: * (1)如果二叉树为空,返回真 * (2)如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假 */ public static boolean isAVLRec(TreeNode root) { if(root == null){ // 如果二叉树为空,返回真 return true; } // 如果左子树和右子树高度相差大于1,则非平衡二叉树, getDepthRec()是前面实现过的求树高度的方法 if(Math.abs(getDepthRec(root.left) - getDepthRec(root.right)) > 1){ return false; } // 递归判断左子树和右子树是否为平衡二叉树 return isAVLRec(root.left) && isAVLRec(root.right); } /** * 求二叉树的镜像 递归解法: * (1)如果二叉树为空,返回空 * (2)如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树和右子树 */ // 1. 破坏原来的树,把原来的树改成其镜像 public static TreeNode mirrorRec(TreeNode root) { if (root == null) { return null; } TreeNode left = mirrorRec(root.left); TreeNode right = mirrorRec(root.right); root.left = right; root.right = left; return root; } // 2. 不能破坏原来的树,返回一个新的镜像树 public static TreeNode mirrorCopyRec(TreeNode root){ if(root == null){ return null; } TreeNode newNode = new TreeNode(root.val); newNode.left = mirrorCopyRec(root.right); newNode.right = mirrorCopyRec(root.left); return newNode; } // 3. 判断两个树是否互相镜像 public static boolean isMirrorRec(TreeNode r1, TreeNode r2){ // 如果两个树都是空树,则返回true if(r1==null && r2==null){ return true; } // 如果有一棵树是空树,另一颗不是,则返回false if(r1==null || r2==null){ return false; } // 如果两个树都非空树,则先比较根节点 if(r1.val != r2.val){ return false; } // 递归比较r1的左子树的镜像是不是r2右子树 和 // r1的右子树的镜像是不是r2左子树 return isMirrorRec(r1.left, r2.right) && isMirrorRec(r1.right, r2.left); } // 1. 破坏原来的树,把原来的树改成其镜像 public static void mirror(TreeNode root) { if(root == null){ return; } Stack<TreeNode> stack = new Stack<TreeNode>(); stack.push(root); while( !stack.isEmpty() ){ TreeNode cur = stack.pop(); // 交换左右孩子 TreeNode tmp = cur.right; cur.right = cur.left; cur.left = tmp; if(cur.right != null){ stack.push(cur.right); } if(cur.left != null){ stack.push(cur.left); } } } // 2. 不能破坏原来的树,返回一个新的镜像树 public static TreeNode mirrorCopy(TreeNode root){ if(root == null){ return null; } Stack<TreeNode> stack = new Stack<TreeNode>(); Stack<TreeNode> newStack = new Stack<TreeNode>(); stack.push(root); TreeNode newRoot = new TreeNode(root.val); newStack.push(newRoot); while( !stack.isEmpty() ){ TreeNode cur = stack.pop(); TreeNode newCur = newStack.pop(); if(cur.right != null){ stack.push(cur.right); newCur.left = new TreeNode(cur.right.val); newStack.push(newCur.left); } if(cur.left != null){ stack.push(cur.left); newCur.right = new TreeNode(cur.left.val); newStack.push(newCur.right); } } return newRoot; } /** * 求二叉树中两个节点的最低公共祖先节点 * 递归解法: * (1)如果两个节点分别在根节点的左子树和右子树,则返回根节点 * (2)如果两个节点都在左子树,则递归处理左子树;如果两个节点都在右子树,则递归处理右子树 */ public static TreeNode getLastCommonParentRec(TreeNode root, TreeNode n1, TreeNode n2) { if (findNodeRec(root.left, n1)) { // 如果n1在树的左子树 if (findNodeRec(root.right, n2)) { // 如果n2在树的右子树 return root; // 返回根节点 } else { // 如果n2也在树的左子树 return getLastCommonParentRec(root.left, n1, n2); // 递归处理 } } else { // 如果n1在树的右子树 if (findNodeRec(root.left, n2)) { // 如果n2在左子树 return root; } else { // 如果n2在右子树 return getLastCommonParentRec(root.right, n1, n2); // 递归处理 } } } // 帮助方法,递归判断一个点是否在树里 private static boolean findNodeRec(TreeNode root, TreeNode node) { if (root == null || node == null) { return false; } if (root == node) { return true; } // 先尝试在左子树中查找 boolean found = findNodeRec(root.left, node); if (!found) { // 如果查找不到,再在右子树中查找 found = findNodeRec(root.right, node); } return found; } // 求二叉树中两个节点的最低公共祖先节点 (更加简洁版的递归) public static TreeNode getLastCommonParentRec2(TreeNode root, TreeNode n1, TreeNode n2) { if(root == null){ return null; } // 如果有一个match,则说明当前node就是要找的最低公共祖先 if(root.equals(n1) || root.equals(n2)){ return root; } TreeNode commonInLeft = getLastCommonParentRec2(root.left, n1, n2); TreeNode commonInRight = getLastCommonParentRec2(root.right, n1, n2); // 如果一个左子树找到,一个在右子树找到,则说明root是唯一可能的最低公共祖先 if(commonInLeft!=null && commonInRight!=null){ return root; } // 其他情况是要不然在左子树要不然在右子树 if(commonInLeft != null){ return commonInLeft; } return commonInRight; } /** * 非递归解法: * 先求从根节点到两个节点的路径,然后再比较对应路径的节点就行,最后一个相同的节点也就是他们在二叉树中的最低公共祖先节点 */ public static TreeNode getLastCommonParent(TreeNode root, TreeNode n1, TreeNode n2) { if (root == null || n1 == null || n2 == null) { return null; } ArrayList<TreeNode> p1 = new ArrayList<TreeNode>(); boolean res1 = getNodePath(root, n1, p1); ArrayList<TreeNode> p2 = new ArrayList<TreeNode>(); boolean res2 = getNodePath(root, n2, p2); if (!res1 || !res2) { return null; } TreeNode last = null; Iterator<TreeNode> iter1 = p1.iterator(); Iterator<TreeNode> iter2 = p2.iterator(); while (iter1.hasNext() && iter2.hasNext()) { TreeNode tmp1 = iter1.next(); TreeNode tmp2 = iter2.next(); if (tmp1 == tmp2) { last = tmp1; } else { // 直到遇到非公共节点 break; } } return last; } // 把从根节点到node路径上所有的点都添加到path中 private static boolean getNodePath(TreeNode root, TreeNode node, ArrayList<TreeNode> path) { if (root == null) { return false; } path.add(root); // 把这个节点加到路径中 if (root == node) { return true; } boolean found = false; found = getNodePath(root.left, node, path); // 先在左子树中找 if (!found) { // 如果没找到,再在右子树找 found = getNodePath(root.right, node, path); } if (!found) { // 如果实在没找到证明这个节点不在路径中,说明刚才添加进去的不是路径上的节点,删掉! path.remove(root); } return found; } /** * 求二叉树中节点的最大距离 即二叉树中相距最远的两个节点之间的距离。 (distance / diameter) * 递归解法: * (1)如果二叉树为空,返回0,同时记录左子树和右子树的深度,都为0 * (2)如果二叉树不为空,最大距离要么是左子树中的最大距离,要么是右子树中的最大距离, * 要么是左子树节点中到根节点的最大距离+右子树节点中到根节点的最大距离, * 同时记录左子树和右子树节点中到根节点的最大距离。 * * http://www.cnblogs.com/miloyip/archive/2010/02/25/1673114.html * * 计算一个二叉树的最大距离有两个情况: 情况A: 路径经过左子树的最深节点,通过根节点,再到右子树的最深节点。 情况B: 路径不穿过根节点,而是左子树或右子树的最大距离路径,取其大者。 只需要计算这两个情况的路径距离,并取其大者,就是该二叉树的最大距离 */ public static Result getMaxDistanceRec(TreeNode root){ if(root == null){ Result empty = new Result(0, -1); // 目的是让调用方 +1 后,把当前的不存在的 (NULL) 子树当成最大深度为 0 return empty; } // 计算出左右子树分别最大距离 Result lmd = getMaxDistanceRec(root.left); Result rmd = getMaxDistanceRec(root.right); Result res = new Result(); res.maxDepth = Math.max(lmd.maxDepth, rmd.maxDepth) + 1; // 当前最大深度 // 取情况A和情况B中较大值 res.maxDistance = Math.max( lmd.maxDepth+rmd.maxDepth, Math.max(lmd.maxDistance, rmd.maxDistance) ); return res; } private static class Result{ int maxDistance; int maxDepth; public Result() { } public Result(int maxDistance, int maxDepth) { this.maxDistance = maxDistance; this.maxDepth = maxDepth; } } /** * 13. 由前序遍历序列和中序遍历序列重建二叉树(递归) * 感觉这篇是讲的最为清晰的: * http://crackinterviewtoday.wordpress.com/2010/03/15/rebuild-a-binary-tree-from-inorder-and-preorder-traversals/ * 文中还提到一种避免开额外空间的方法,等下次补上 */ public static TreeNode rebuildBinaryTreeRec(List<Integer> preOrder, List<Integer> inOrder){ TreeNode root = null; List<Integer> leftPreOrder; List<Integer> rightPreOrder; List<Integer> leftInorder; List<Integer> rightInorder; int inorderPos; int preorderPos; if ((preOrder.size() != 0) && (inOrder.size() != 0)) { // 把preorder的第一个元素作为root root = new TreeNode(preOrder.get(0)); // Based upon the current node data seperate the traversals into leftPreorder, rightPreorder, // leftInorder, rightInorder lists // 因为知道root节点了,所以根据root节点位置,把preorder,inorder分别划分为 root左侧 和 右侧 的两个子区间 inorderPos = inOrder.indexOf(preOrder.get(0)); // inorder序列的分割点 leftInorder = inOrder.subList(0, inorderPos); rightInorder = inOrder.subList(inorderPos + 1, inOrder.size()); preorderPos = leftInorder.size(); // preorder序列的分割点 leftPreOrder = preOrder.subList(1, preorderPos + 1); rightPreOrder = preOrder.subList(preorderPos + 1, preOrder.size()); root.left = rebuildBinaryTreeRec(leftPreOrder, leftInorder); // root的左子树就是preorder和inorder的左侧区间而形成的树 root.right = rebuildBinaryTreeRec(rightPreOrder, rightInorder); // root的右子树就是preorder和inorder的右侧区间而形成的树 } return root; } /** 14. 判断二叉树是不是完全二叉树(迭代) 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数, 第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。 有如下算法,按层次(从上到下,从左到右)遍历二叉树,当遇到一个节点的左子树为空时, 则该节点右子树必须为空,且后面遍历的节点左右子树都必须为空,否则不是完全二叉树。 */ public static boolean isCompleteBinaryTree(TreeNode root){ if(root == null){ return false; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); boolean mustHaveNoChild = false; boolean result = true; while( !queue.isEmpty() ){ TreeNode cur = queue.remove(); if(mustHaveNoChild){ // 已经出现了有空子树的节点了,后面出现的必须为叶节点(左右子树都为空) if(cur.left!=null || cur.right!=null){ result = false; break; } } else { if(cur.left!=null && cur.right!=null){ // 如果左子树和右子树都非空,则继续遍历 queue.add(cur.left); queue.add(cur.right); }else if(cur.left!=null && cur.right==null){ // 如果左子树非空但右子树为空,说明已经出现空节点,之后必须都为空子树 mustHaveNoChild = true; queue.add(cur.left); }else if(cur.left==null && cur.right!=null){ // 如果左子树为空但右子树非空,说明这棵树已经不是完全二叉完全树! result = false; break; }else{ // 如果左右子树都为空,则后面的必须也都为空子树 mustHaveNoChild = true; } } } return result; } /** * 14. 判断二叉树是不是完全二叉树(递归) * http://stackoverflow.com/questions/1442674/how-to-determine-whether-a-binary-tree-is-complete * */ public static boolean isCompleteBinaryTreeRec(TreeNode root){ // Pair notComplete = new Pair(-1, false); // return !isCompleteBinaryTreeSubRec(root).equalsTo(notComplete); return isCompleteBinaryTreeSubRec(root).height != -1; } // 递归判断是否满树(完美) public static boolean isPerfectBinaryTreeRec(TreeNode root){ return isCompleteBinaryTreeSubRec(root).isFull; } // 递归,要创建一个Pair class来保存树的高度和是否已满的信息 public static Pair isCompleteBinaryTreeSubRec(TreeNode root){ if(root == null){ return new Pair(0, true); } Pair left = isCompleteBinaryTreeSubRec(root.left); Pair right = isCompleteBinaryTreeSubRec(root.right); // 左树满节点,而且左右树相同高度,则是唯一可能形成满树(若右树也是满节点)的情况 if(left.isFull && left.height==right.height){ return new Pair(1+left.height, right.isFull); } // 左树非满,但右树是满节点,且左树高度比右树高一 // 注意到如果其左树为非完全树,则它的高度已经被设置成-1, // 因此不可能满足第二个条件! if(right.isFull && left.height==right.height+1){ return new Pair(1+left.height, false); } // 其他情况都是非完全树,直接设置高度为-1 return new Pair(-1, false); } private static class Pair{ int height; // 树的高度 boolean isFull; // 是否是个满树 public Pair(int height, boolean isFull) { this.height = height; this.isFull = isFull; } public boolean equalsTo(Pair obj){ return this.height==obj.height && this.isFull==obj.isFull; } } }
面试大总结之二:Java搞定面试中的二叉树题目的更多相关文章
- (转)面试大总结之一:Java搞定面试中的链表题目
面试大总结之一:Java搞定面试中的链表题目 分类: Algorithm Interview2013-11-16 05:53 11628人阅读 评论(40) 收藏 举报 链表是面试中常出现的一类题目, ...
- 面试大总结:Java搞定面试中的链表题目总结
package LinkedListSummary; import java.util.HashMap; import java.util.Stack; /** * http://blog.csdn. ...
- Java原来还可以这么学:如何搞定面试中必考的集合类
原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 系列文章介绍 本文是<五分钟学Java>系列文章的一篇 本系列文章主要围绕Java程序员必须掌握的核心技能,结合我个人三年 ...
- 基于vue+springboot+docker网站搭建【二】搞定服务器
搞定服务器 双11在阿里云用家人的身份证注册账号,买了两台打折的服务器.2核4G一台3年799块:1核2G一台3年229块.机器配置如下图. 1.买的时候注意相同地区的相同可用区.比如我两台机器都选择 ...
- 【搞定面试官】- Synchronized如何实现同步?锁优化?(1)
前言 说起Java面试中最高频的知识点非多线程莫属.每每提起多线程都绕不过一个Java关键字--synchronized.我们都知道该关键字可以保证在同一时刻,只有一个线程可以执行某个方法或者某个代码 ...
- 【搞定面试官】谈谈你对JDK中Executor的理解?
## 前言 随着当今处理器计算能力愈发强大,可用的核心数量越来越多,各个应用对其实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行.在此背景下,Java自JDK1.5 提供了自己的多线程框架 ...
- 如何用python搞定验证码中的噪点
背景:朋友在为"关山口男子职业技术学校"写一款校园应用,于是找MoonXue写一个学生选课系统的登录接口.为了搞定这个接口,不得不先搞定这个系统的验证码. 验证码大概是这个样子 看 ...
- 三步轻松搞定delphi中CXGRID手动添加复表头(多行表头,报表头)
网上有代码动态生成cxgrid多行表头的源码,地址为:http://mycreature.blog.163.com/blog/static/556317200772524226400/ 如果要手动设计 ...
- Cordova - 彻底搞定安卓中的微信支付插件!
Cordova:8.0.0 Android studio:3.2.1 cordova-plugin-adam-wechat : 3.0.6 你看到这个标题肯定会惊讶,一个Cordova的微信支付插件, ...
随机推荐
- [GeekBand] 面向对象的设计模式(C++)(2)
本篇笔记紧接上篇,继续学习设计模式. 4. 对象创建类设计模式 通过对象创建模式绕开new,来避免对象创建(new)过程中所导致的紧耦合,从而支持对象创建的稳定.它是接口抽象之后的第一步工作. 4.1 ...
- item44:将与参数无关的代码抽离template
编写non-template代码中,重复十分明显:可以很直观的看到代码的重复,然后将它们写成一个新的class或者函数,然后供调用. 编写template代码中,重复是隐晦的:只存在一份templat ...
- Web前端新人笔记之了解Jquery
与javaScript相比,Jquery更简洁.浏览器的兼容性更强,语法更灵活,对xpath的支持更强大.一个$符就可以遍历文档中各级元素.例:在页面上有一个无序列表,我们需要将所有列表项中的文本内容 ...
- VS2005打开VS2008 VS2010 VS2012
我用vs2005较多,但网上找的代码经常是08 10 或者2012的,总结了以下技巧可以打开工程比较小巧的高版本代码. <1>用记事本打开解决方案文件“解决方案名.sln”,然后修改最上面 ...
- WPF中Expander控件样式,ListBox的样式(带checkbox)恢复
Expander控件样式: <ControlTemplate x:Key="ExpanderToggleButton" TargetType="ToggleButt ...
- CSS溢出文本省略(text-overflow)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- 通过 struct 成员地址 获取 struct 结构体地址
1. 问题描述: 现在定义了一个结构体: struct Foo { int a; int b; }; Foo foo; 假如由于函数传参等原因,现在程序只能拿到 foo.b 的地址,这时想通过某种方法 ...
- Technical diagrams for SharePoint 2013
sharepoint2013技术图表 http://technet.microsoft.com/zh-cn/library/cc263199.aspx SharePoint 2013 的可下载内容 h ...
- 怎样修改Windows7环境变量
在使用电脑的时候要运行某些特定的应用程序时需要修改系统的环境变量,例如安装JAVA时我们就需要配置系统的环境变量.那什么是环境变量呢?环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,比 ...
- wordpress换域名后无法登陆的解决方案
第一步:登录到你的数据库管理页面,找到wp_options表: 第二步:将表中的siteurl和home字段的值修改为当前的新域名,siteurl值的修改和home值的修改同理.如下图: