树
105. 从前序与中序遍历序列构造二叉树
-
思路
-
先序遍历中,树的根节点放在第一位,后面是左右子树。中序遍历中,树的根节点放在中间,两边分为左右子树
-
可基于以上规则区分出每棵树在数组中的区间
- 先从先序数组中拿到根节点的val
- 因为val不重复,定位到该树在中序数组的位置
- 中序数组根节点位置确定,可以计算左右子树长度
- 根据左右子树长度,可以定位出在先序数组中的位置
-
能区分出树的区间,可用递归构建出树
- 终止条件。区间内没有元素,无法构造出树
- 当前层。构建根节点,计算左右子树区间,递归调用构造左右子树
- 递归函数。入参为 树的先序数组、中序数组;出参为子树的根节点
-
-
代码
java// pre 中 根节点在第一位,in 中 根节点的左边为 左子树,右边为右子树。可根据此规则区分出树的范围 // 使用递归不断缩减树的范围,当没有子节点时则可以构造节点返回 private Map<Integer,Integer> treeMap = new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { for(int i = 0; i < inorder.length; i ++){ treeMap.put(inorder[i],i); } return buildNode(preorder,0,preorder.length-1,inorder,0,inorder.length-1); } public TreeNode buildNode(int[] preorder,int preBegin,int preEnd,int[] inorder,int inBegin,int inEnd){ // 终止条件 if(preBegin > preEnd && inBegin > inEnd){ return null; } // 当前层处理 int intVal = preorder[preBegin]; TreeNode root = new TreeNode(intVal); int inIndex = treeMap.get(intVal); int leftLength = inIndex - inBegin; // 递归调用 root.left = buildNode(preorder,preBegin+1,preBegin+leftLength,inorder,inBegin,inIndex-1); //注意点一 root.right = buildNode(preorder,preBegin+leftLength+1,preEnd,inorder,inIndex+1,inEnd); return root; }
-
注意点
- 注意点一,统一入参中索引构建的是左闭右闭区间 [begin,end]
117. 填充每个节点的下一个右侧节点指针 II
-
思路
- 利用树的层序遍历。可获取到每一层的全部节点,修改next指针进行指向即可
-
代码
java// 层序遍历。可单独获取到每一层的节点,遍历每一层的节点填充next属性即可 public Node connect(Node root) { if(root == null) return null; Queue<Node> queue = new LinkedList<>(); queue.offer(root); while(!queue.isEmpty()){ int size = queue.size(); Node last = null; // 每一层的处理 while(size-- > 0){ Node curr = queue.poll(); if(curr.left != null){ queue.offer(curr.left); } if(curr.right != null){ queue.offer(curr.right); } // 非每一行的首个节点 if(last != null){ last.next = curr; } last = curr; } } return root; }
236. 二叉树的最近公共祖先
- 解题思路
- root为p、q的最近公共祖先,可能为以下情况
- p、q分别在root的左右子树
- root等于p,q在root的左/右子树
- root等于q,p在root的左/右子树
- 用先序遍历递归来判断每个节点是否为最近公共祖先
- 终止条件
- root为null / p / q 时,root节点可能成为最近祖先,依赖root相同父节点的另外一个子节点的判断情况决定
- 递归函数
- 定义为,找到当前树中,可能存在最近祖先的节点
- 当前层处理
- 递归当前节点的左右子树,单一侧找不到 有可能成为祖先的,则另外一的成为祖先
- 终止条件
- root为p、q的最近公共祖先,可能为以下情况
- 代码
java
// 先序遍历。找到空节点/val=p/val=q即可返回
// 当左子树找到其中一个,另外一个为空,则说明只能出现在左子树/父节点
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root.val == p.val || root.val == q.val){
return root;
}
TreeNode left = lowestCommonAncestor(root.left,p,q); // 注意点一
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left == null){
return right;
}
if(right == null){
return left;
}
return root;
}
- 注意点
- lowestCommonAncestor方法的定义发生了改变,不是找到最佳祖先,而且可能成为的祖先
129. 求根节点到叶节点数字之和
-
思路
- 根节点到当前节点的数字 = 根到父节点的数 * 10 + 当前节点的数
- 利用深度优先搜索,搜索到叶子节点,计算数字进行相加
-
代码
java
public int sumNumbers(TreeNode root) {
return dfs(root,0);
}
public int dfs(TreeNode root,int preSum){
if(root == null){
return 0;
}
int sum = preSum * 10 + root.val;
if(root.left == null && root.right == null){
return sum;
}else{
return dfs(root.left,sum) + dfs(root.right,sum);
}
}
}
前中后序遍历
递归写法
- 思路
- 以先序为例,调整处理中左右节点的顺序即可
java
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorder(root, result);
return result;
}
public void preorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
// 处理中节点
result.add(root.val);
// 处理左子节点
preorder(root.left, result);
// 处理右子节点
preorder(root.right, result);
}
迭代写法
-
思路
- 用压栈来模拟递归方法的调用。调用递归方法就将就是将该节点压入栈,元素再弹出时再对节点进行当前层的处理
- 因为栈为后入先出,当前层的处理中,遍历的顺序为左中右,压栈的处理顺序就得为右中左
-
代码
java
// 中后序
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stk = new Stack<>();
stk.push(root);
while(!stk.isEmpty()){
TreeNode curr = stk.pop();
if(curr == null){
res.add(stk.pop().val);
continue;
}
// 右
if(curr.right != null){
stk.push(curr.right);
}
// 中
stk.push(curr);
stk.push(null); // 注意点一
// 左
if(curr.left != null){
stk.push(curr.left);
}
}
return res;
}
// 先序
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null){
return res;
}
Stack<TreeNode> stk = new Stack<>();
stk.push(root);
while(!stk.isEmpty()){
// 当前节点可以直接处理
TreeNode curr = stk.pop();
res.add(curr.val);
// 需要递归处理的变量,倒序放入到栈
if(curr.right != null){
stk.push(curr.right);
}
if(curr.left != null){
stk.push(curr.left);
}
}
return res;
}
- 注意
- 注意点一。当前层处理中将子节点压栈的同时,需要再次将根节点入栈,并添加null节点作为标记。
530. 二叉搜索树的最小绝对差
- 思路
- 二叉搜索树的中序遍历就是一个有序数组
- 根据以上,可以在中序遍历的过程中,保存上一个节点,并计算和上个节点的差
- 代码
java
public int getMinimumDifference(TreeNode root) {
int res = Integer.MAX_VALUE;
Stack<TreeNode> stk = new Stack<>();
stk.push(root);
TreeNode pre = null;
while(!stk.isEmpty()){
TreeNode curr = stk.pop();
if(curr == null){
curr = stk.pop();
// 遍历过程计算上一个的差
if(pre != null){
res = Math.min(Math.abs(curr.val - pre.val),res);
}
pre = curr;
continue;
}
// 右
if(curr.right != null){
stk.push(curr.right);
}
// 中
stk.push(curr);
stk.push(null);
// 左
if(curr.left != null){
stk.push(curr.left);
}
}
return res;
}