第六章 二叉树 part05
今日内容
● 513.找树左下角的值
● 112. 路径总和 113.路径总和ii
● 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树 详细布置
513.找树左下角的值
题目具体位置:513.找树左下角的值)
暴力笨蛋迭代方法——迭代层序遍历 (Java能通过,但是很慢)
主要思想: 通过层序遍历,遍历树的每一层的节点,并且在遍历每一层的时候,都通过result 来记录当前层最左边的那个节点的值。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
//考虑尝试使用层序遍历来找到最大层数的最左边节点的值
//一定是叶子节点
class Solution {
public int findBottomLeftValue(TreeNode root) {
//迭代法层序遍历,依然还是要使用到队列
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
int result=0; //期望每次通过result来记录每一层的最左侧的节点
while(!queue.isEmpty()){
//获取当前层的节点个数
int len=queue.size();
//result来记录当前层最左侧的节点的值
result=queue.peek().val;
//把当前层所有节点的左右子节点均加入到队列中
while(len>0){
TreeNode curNode=queue.poll();
if(curNode.left!=null)
queue.offer(curNode.left);
if(curNode.right!=null)
queue.offer(curNode.right);
len--;
}
}
return result;
}
}
回溯法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int result;
int maxDepth=Integer.MIN_VALUE;
public int findBottomLeftValue(TreeNode root) {
traversal(root,1);
return result;
}
//使用递归方法来完成求解
//
public void traversal(TreeNode node,int depth){
//一旦第一次遇到叶子节点,就进行记录
//那么这里是如何控制所谓的“第一次”呢? 就是用depth> maxDepth这个条件
//因为如果之前已经遇到过了同层的左侧叶子节点,其maxDepth就会更新,之后回溯再遇到的话,就不会再满足depth>maxDepth这个条件
if(node.left==null && node.right==null && depth>maxDepth){
maxDepth=depth;
result=node.val;
}
//向左递归遍历
if(node.left!=null){
depth++;
traversal(node.left,depth);
depth--;
}
//向右递归遍历
if(node.right!=null){
depth++;
traversal(node.right,depth);
depth--;
}
}
}
这里核心代码可以简化为:
//向左递归遍历
if(node.left!=null){
traversal(node.left,depth+1);
}
//向右递归遍历
if(node.right!=null){
traversal(node.right,depth+1);
}
这里就是涉及到理解递归内部的回溯: 递归本身就带有“回溯”在里面,个人理解,每一轮的递归,就相当于是将临时变量用一个栈存储起来,然后递归自己返回(or 回溯)上一轮的时候,上一轮的临时变量又会从栈中pop出来。
112.路径总和、113. 路径总和ii
题目112.路径总和
具体代码如下所示:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
boolean result=false;
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null)
return false;
findPath(root,0,targetSum);
return result;
}
//考虑使用递归法-前序遍历来完成
//前序遍历--中左右
public void findPath(TreeNode node,int sum,int targetSum){
if(node==null)
return;
//如果当前节点是叶子结点并且sum==targetSum
if(node.left==null&& node.right==null && sum+node.val==targetSum){
result=true;
}
findPath(node.left,sum+node.val,targetSum);
findPath(node.right,sum+node.val,targetSum);
}
}
解题思想主要几个点:
-
为什么使用前序遍历(中左右)?因为寻找这条路径的过程,本身就需要将当前节点的值给加进去,所以势必要先处理当前遇到的节点,然后再去遍历。故而使用前序遍历(中左右)
-
递归三要素:递归返回值、形参、每一轮递归处理逻辑。这三个方面是如何敲定的?
-
递归返回值:根据题意,只需要返回true 或 false,这里我就直接将result设置成了成员变量,它是全局的,所以不需要再在递归函数里面返回什么东西,只需要直接改变result的值就行了。
-
形参:TreeNode node 一定要的,毕竟是遍历二叉树;sum,记录从根节点遍历到当前节点的总和; targetSum ,因为每一轮都要用来比较,然而targetSum有没有办法和sum一样作为“全局变量”,所以每一轮都要进行传递。
-
每一轮递归处理逻辑:最重要的就是找到叶子结点然后看这一整条路径的总和是否符合targetSum,以此来进行判断。 至于向左递归以及向右递归不提前判断是否为空,是因为这无伤大雅,毕竟在递归函数第一行就判断了,如果为空就return.
-
-
回溯:同上面那个题,当前节点传递到下一轮的递归过程中,形参为sum+val 这样就传递下去了,当递归返回的时候,本层的sum又会从栈里面弹出,故而这样是可行的。
113.路径总和ii
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<List<Integer>> result=new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if(root==null)
return result;
List<Integer> path=new ArrayList<>();
traversal(root,0,targetSum,path);
return result;
}
//考虑使用递归方法——前序遍历 中左右
public void traversal(TreeNode node,int sum,int targetSum,List<Integer> path){
if(node==null)
return;
//不管是不是叶子节点 每次碰到了一个非空的节点 都要将这个节点值存入path
path.add(node.val);
//如果是叶子节点并且这一条路径下来sum==targetSum
//将这一条队列加入到result中
if(node.left==null && node.right==null && sum+node.val==targetSum){
//因为path一直是不断变化的,所以需要用path来重新构造一个ArrayList
//要不然的话 如果是单纯result.add(path),path一变化 result里面存储的也就会跟着变化
result.add(new ArrayList<>(path));
}
if(node.left!=null){
traversal(node.left,sum+node.val,targetSum,path);
path.remove(path.size()-1); //每一轮回退 都要将path手动回退 因为他是列表
}
if(node.right!=null){
traversal(node.right,sum+=node.val,targetSum,path);
path.remove(path.size()-1);
}
}
}
上面的代码最重要的两个点:特别需要注意
-
第一个点就是将path加入到result之中的时候,必须要调用ArrayList的构造函数,将path作为参数重新构造一个ArrayList传入到result中
-
第二个点就是:为什么我们需要手动地去调整path,难道它不能自动“回溯”吗?
-
之所以需要手动调整,是因为在Java中,对象是通过引用传递的,而不是通过值传递的。也就是说在递归回溯的时候,path是不会自动变回上一个状态的。
-
其次正是因为要手动调path,那么就需要判断node.left 或者 node.right是否为空,不然得话手动remove path的元素 ,可能会报错
-
106 从中序与后序遍历序列构造二叉树
仅用于个人调试,下面的代码并不具备提交到力扣的能力。
package com.BinaryTree;
import java.util.ArrayList;
import java.util.List;
public class demo {
public static void main(String[] args) {
int[] inorder={9,3,15,20,7};
int[] postorder={9,15,7,20,3};
traversal(inorder,0,inorder.length,postorder,0,postorder.length);
}
//考虑使用递归来完成二叉树的构造
public static TreeNode traversal(int[] inorder,int inBegin,int inEnd,int[] postorder,int postBegin,int postEnd){
//如果后续是空的 那么整棵树都是空的
if(postorder.length==0)
return null;
//首先获取后序遍历数组中最后一个节点,就是根节点
int rootVal=postorder[postEnd-1];
TreeNode root=new TreeNode(rootVal);
//如果后序遍历数组只有一个元素 ,那么整棵树就只有一个节点————根节点
if(postEnd-postBegin==1)
return root;
//如果不止一个节点,那么首先对中序遍历数组进行切割
//找到“根节点”的value在中序遍历中的位置
int delimiterIndex=0;
for(int i=inBegin;i<inEnd;i++){
if(inorder[i]==rootVal){
delimiterIndex=i;
break;
}
}
//以下都是左闭右开切割
//找到了中序的分界点delimiterIndex了 那么首先对inorder进行切割
int leftInorderBegin=inBegin;
int leftInorderEnd=delimiterIndex;
int rightInorderBegin=delimiterIndex+1;
int rightInorderEnd=inEnd;
//然后再对后续遍历数组进行切割
//因为delimiter只是在中序遍历中的下标,但是这个下标并不适用于后序遍历
//切割后序遍历的思想就是———— 我后序遍历切割出来的左子树与中序遍历的数组大小一样 右边也是一样的概念
//那么leftPostEnd就看是leftPostBegin需要移动几个元素
int leftPostBegin=postBegin;
int leftPostEnd=postBegin+delimiterIndex-inBegin;
int rightPostBegin=postBegin+(delimiterIndex-inBegin);
int rightPostEnd=postEnd-1;
System.out.println("leftInorder");
for(int i=leftInorderBegin;i<leftInorderEnd;i++){
System.out.print(inorder[i] + " ");
}
System.out.println();
System.out.println("RightInorder");
for(int i=rightInorderBegin;i<rightInorderEnd;i++){
System.out.print(inorder[i] + " ");
}
System.out.println();
System.out.println("LeftPostOrder");
for(int i=leftPostBegin;i<leftPostEnd;i++){
System.out.print(postorder[i]+" ");
}
System.out.println();
System.out.println("rightPostOrder");
for (int i=rightPostBegin;i<rightPostEnd;i++){
System.out.print(postorder[i]+" ");
}
System.out.println();
root.leftNode=traversal(inorder,leftInorderBegin,leftInorderEnd,postorder,leftPostBegin,leftPostEnd);
root.rightNode=traversal(inorder,rightInorderBegin,rightInorderEnd,postorder,rightPostBegin,rightPostEnd);
return root;
}
}