作者简介:大家好,我是未央;
博客首页:未央.303
系列专栏:牛客面试必刷TOP101
每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!!!!
前言
一、BM38 在二叉树中找到两个节点的最近公共祖先
题目描述
描述:
给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。
举例说明:
如当输入{3,5,1,6,2,0,8,#,#,7,4},5,1时,二叉树{3,5,1,6,2,0,8,#,#,7,4}如下图所示:
所以节点值为5和节点值为1的节点的最近公共祖先节点的节点值为3,所以对应的输出为3。
节点本身可以视为自己的祖先.
示例1:
示例2:
题目解析
方法:递归
知识点:二叉树递归
二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。
思路:
我们也可以讨论几种情况:
- step 1:如果o1和o2中的任一个和root匹配,那么root就是最近公共祖先。
- step 2:如果都不匹配,则分别递归左、右子树。
- step 3:如果有一个节点出现在左子树,并且另一个节点出现在右子树,则root就是最近公共祖先.
- step 4:如果两个节点都出现在左子树,则说明最低公共祖先在左子树中,否则在右子树。
- step 5:继续递归左、右子树,直到遇到step1或者step3的情况。
代码解析:
import java.util.*; public class Solution { //求得根节点到目标节点的路径 public ArrayList<Integer> getPath(TreeNode root, int target) { //1.首先定义一个ArrayList类型的变量path,用于存储路径节点的值。 ArrayList<Integer> path = new ArrayList<Integer>(); //2.将根节点赋值给一个临时变量node。 TreeNode node = root; //3.使用while循环,判断当前节点的值是否等于目标节点的值 while (node.val != target) { path.add(node.val); //如果不等于,则将当前节点的值添加到path中,并根据目标节点的值与当前节点的值的大小关系,更新node为其左子节点或右子节点。 //小的在左子树 if (target < node.val) node = node.left; //大的在右子树 else node = node.right; } //当循环结束后,将当前节点的值添加到path中。 path.add(node.val); //返回path作为根节点到目标节点的路径。 return path; } public int lowestCommonAncestor (TreeNode root, int p, int q) { //4.求根节点到两个节点的路径 //在lowestCommonAncestor函数中,分别调用getPath函数,得到根节点到节点p和节点q的路径,分别保存在变量path_p和path_q中。 ArrayList<Integer> path_p = getPath(root, p); ArrayList<Integer> path_q = getPath(root, q); //定义一个变量res,用于保存结果 int res = 0; //5.比较两个路径,找到第一个不同的点 //使用for循环,遍历path_p和path_q,比较两个路径的每个节点的值,找到第一个不同的节点. for (int i = 0; i < path_p.size() && i < path_q.size(); i++) { int x = path_p.get(i); int y = path_q.get(i); //6.最后一个相同的节点就是最近公共祖先 if (x == y) res = path_p.get(i); else break; } //6.跳出循环。 //返回res作为最近公共祖先的值。 return res; } }
二、BM40 重建二叉树
题目描述
描述:
给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
举例说明:
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。
示例1:
示例2:
示例3:
题目解析
方法:递归
知识点:二叉树递归
二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。
思路:
对于二叉树的前序遍历,我们知道序列的第一个元素必定是根节点的值,因为序列没有重复的元素,因此中序遍历中可以找到相同的这个元素,而我们又知道中序遍历中根节点将二叉树分成了左右子树两个部分,如下图所示:
我们可以发现,数字1是根节点,并将二叉树分成了(247)和(3568)两棵子树,而子树的的根也是相应前序序列的首位,比如左子树的根是数字2,右子树的根是数字3,这样我们就可以利用前序遍历序列找子树的根节点,利用中序遍历序列区分每个子树的节点数。
具体做法:
- step 1:先根据前序遍历第一个点建立根节点。
- step 2:然后遍历中序遍历找到根节点在数组中的位置。
- step 3:再按照子树的节点数将两个遍历的序列分割成子数组,将子数组送入函数建立子树。
- step 4:直到子树的序列长度为0,结束递归。
代码解析:
import java.util.*; public class Solution { //1.定义一个函数reConstructBinaryTree,用于重建二叉树。 public TreeNode reConstructBinaryTree(int [] pre, int [] vin) { //该函数接收两个参数,一个是前序遍历序列pre,一个是中序遍历序列vin。 int n = pre.length; int m = vin.length; //2.首先判断前序遍历序列和中序遍历序列的长度是否为0,如果任一序列长度为0,说明没有节点可以构建二叉树,返回null if (n == 0 || m == 0) return null; //3.创建根节点,节点的值为前序遍历序列的第一个值pre[0] TreeNode root = new TreeNode(pre[0]); for (int i = 0; i < vin.length; i++) { //4.然后,在中序遍历序列vin中找到前序遍历序列的第一个元素pre[0]的位置i. if (pre[0] == vin[i]) { //利用Arrays.copyOfRange方法,将前序遍历序列和中序遍历序列分别切割成左子树的前序遍历序列、左子树的中序遍历序列、右子树的前序遍历序列、右子树的中序遍历序列。 //5.递归调用reConstructBinaryTree函数构建根节点的左子树和右子树。 //构建左子树 root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(vin, 0, i)); //构建右子树 root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(vin, i + 1, vin.length)); break; } } //6.最后,返回根节点作为重建的二叉树的根节点 return root; } }