二叉树习题3:左叶子之和、找树左下角的值、路径总和、路径总和||、从中序与后序遍历序列构造二叉树、从前序与中序遍历序列构造二叉树、最大二叉树

左叶子之和

例题404:给定二叉树的根节点 root ,返回所有左叶子之和。
示例

class Solution {
      int res=0;
public int sumOfLeftLeaves(TreeNode root){
    Traversal(root);
    return res;
    }

    public void Traversal(TreeNode root){
        int lz=0;
        if(root.left!=null){//左孩子
            Traversal(root.left);
            if(root.left.left==null && root.left.right==null){//左叶子
            lz=root.left.val;
            }
        }
        if(root.right!=null){
            Traversal(root.right); //右
        }
        res+=lz;//中
    }
}

1.就是在遍历的过程中判断左叶子节点,将其值加起来;
2.左叶子不能在当前节点判断,只能在当前节点判断该左孩子不为空,且左孩子无孩子的才是左叶子。

找树左下角的值

例题513:给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。
示例

class Solution {
    public int findBottomLeftValue(TreeNode root) {
  //层序遍历找到最底层第一个元素
      int res=levelTraversal(root);
      return res;
    }

    public int levelTraversal(TreeNode root){
    Queue<TreeNode> que=new LinkedList<TreeNode>();
    int fir,res=0;
    //List<List<Integer>> z=new LinkedList<List<Integer>>();
    if(root!=null) que.add(root);
    while(!que.isEmpty()){
        //List<Integer> z1=new LinkedList<>();
        int len=que.size();
        fir=len;
        while(len>0)
        {
            TreeNode curNode=que.poll();
            if(len==fir) res=curNode.val;//记录最后一行第一个元素
            if(curNode.left!=null) que.add(curNode.left);
             if(curNode.right!=null) que.add(curNode.right);
            //z1.add(curNode.val);
            len--;
        }
        //z.add(z1);
    }
    //return z.get(z.size()-1).get(0);
    return res;
    }
}

//递归:找到最左边的叶子不一定是最后一层,所以要在递归时计算深度,深度最大的左叶子才是需要的节点。所以递归参数需要节点和深度。

public class findBottomLeftValue {
  public static void main(String[] args){
  }
    }
    class Solution {
    
    int maxDepth = INT_MIN;
    int result;
    
      int findBottomLeftValue(TreeNode root) {
        traversal(root, 0);
        return result;
    }
    

    void traversal(TreeNode root, int depth) {
        if (root.left == NULL && root.right == NULL) {//找到深度最大的叶子节点
            if (depth > maxDepth) {
                maxDepth = depth;
                result = root.val;
            }
            return;
        }
        if (root.left!=null) {
            depth++;
            traversal(root.left, depth);
            depth--; // 回溯
        }
        if (root.right!=null) {
            depth++;
            traversal(root.right, depth);
            depth--; // 回溯
        }
        return;
    }
}

路径总和

例题112:给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

叶子节点 是指没有子节点的节点。

在这里插入图片描述
在这里插入图片描述
找到一条符合要求的路径,所以递归需要返回值,这里的方法用了全局变量,没有使用返回值。但要与下题作比较。
题解

class Solution {
    List<Integer> path=new ArrayList<>();
    List<Integer> res=new ArrayList<>();//存放每个路径和
    public boolean hasPathSum(TreeNode root, int targetSum) {
//递归+回溯
if(root==null) return false;
        Traversal(root,path,res);
        if(res.indexOf(targetSum)==-1) return false;
        else return true;
    }

    public void Traversal(TreeNode root, List<Integer> path,List<Integer> res){
        path.add(root.val);//中
        //停止条件
        if(root.left==null && root.right==null){//找到叶子节点后进入处理逻辑(将这条路径和加起来)
            int sum=0;
            for(int i=0;i<path.size();i++){
                sum+=path.get(i);
            }
            res.add(sum);
            return;
        }
        if(root.left!=null){
            Traversal(root.left,path,res);
            path.remove(path.size()-1);//回溯
        }
        if(root.right!=null){
            Traversal(root.right,path,res);
            path.remove(path.size()-1);
        }
}
}

1.遇到需要遍历树中所有路径的,都需要用到递归(前中后序都可以,因为中间节点没有处理逻辑)+回溯;
2.java中查找集合中的元素位置函数:indexOf(),如果找到返回下标,否则返回-1表示未找到该元素。
3.也可以把targetSum作为参数传递,每次遍历节点就减去该节点值,直到targetSum变为0且该节点为叶子才true。
4.迭代法可以用一对pair<TreeNode root,int val>来存节点以及其值,在遍历过程中依次加节点值与targetSum比较。

路径总和||

例题113:给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。
示例
题解

class Solution {
    List<Integer> path=new ArrayList<>();
    List<List<Integer>> res=new ArrayList<List<Integer>>();
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
    Traversal(root,path,res,targetSum);
    return res;
    }

    public void Traversal(TreeNode root,List<Integer> path,List<List<Integer>> res,int targetSum){
        if(root==null) return;
        path.add(root.val);
        //停止条件:遇到叶子节点后看该路径和是否符合
        if(root.left==null && root.right==null){
         int sum=0;
         for(int i=0;i<path.size();i++){
             sum+=path.get(i);
         }
         if(sum==targetSum){
             //res.add(path);结果错误只有每个集合的第一位
             res.add(new ArrayList<>(path));//需要新建集合空间
         }
         return;
        }
        if(root.left!=null){
            Traversal(root.left,path,res,targetSum);
            path.remove(path.size()-1);
        }
        if(root.right!=null){
            Traversal(root.right,path,res,targetSum);
            path.remove(path.size()-1);
        }
}
}

这道题与上题路径总和类似,都需要用到递归+回溯,但112题不需要遍历完整个树需要返回值,113题需要遍历完整个树所以不需要返回值。这两题学会怎么构造递归

从中序与后序遍历序列构造二叉树

例题106:给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
示例
如何根据两个顺序构造一颗唯一的二叉树,原理如下:
根据后序最后一个元素作为切割点(根节点),先切割中序数组,再根据中序数组反过来切割后序数组,一层一层切下去,每次后序数组最后一个元素就是节点元素。
流程图
根据以上流程原理,可以写出递归的框架

TreeNode traversal(int[] inorder,int[] postorder){
//第一步
if(postorder.size()==0) return root;
//第二步
int rootValue=postorder[postorder.size()-1];
TreeNode root=new TreeNode(rootValue);

//叶子节点
if(postorder.size()==1) return root;

//第三步:找切割点
for(int delimiterIndex=0;delimiterIndex<inorder.size();delimiterIndex++){
if(rootValue==inorder.get(delimiterIndex)) break;

//第四步:切割中序数组,得到中序左数组和中序右数组
//第五步:切割后序数组,得到后序左数组和后序右数组

//第六步
root.left=traversal(中序左数组,后序左数组);
root.right=traversal(中序右数组,后序右数组);

return root;
}

难点就在于切割,注意切割的标准,是左闭右开、还是左开右闭、左闭右闭,在递归时要保持不变。以左闭右开为例。

//第三步:找切割点
for(int delimiterIndex=0;delimiterIndex<inorder.size();delimiterIndex++){
if(rootValue==inorder.get(delimiterIndex)) break;

//第四步:切割中序数组,得到中序左数组和中序右数组
//中序左数组:[0,delimiterIndex)
int[] leftInorder(inorder.begin(),inorder.begin()+delimiterIndex);
//中序右数组:[delimiterIndex+1,end)
int[] rightInorder(inorder.begin()+delimiterIndex+1,inorder.end());

切割后序数组最后一个元素不需要了,这是切割点也就是二叉树中间的元素。后序数组的切割点怎么找呢?
中序数组和后序数组的大小一定相等,所以后序数组可以按照中序左右数组来切割。

//舍弃后序数组最后一位
postorder.resize(postorder.size()-1);

//左闭右开,使用中序左数组的大小作为切割点:[0,leftInorder.size())
int[] leftPostorder(postorder.begin(),postorder.begin()+leftInorder.size());
//[leftInorder.size(),end)
int[] rightPostorder(postorder.begin()+leftInorder.size(),postorder.end());

此时,中序数组切割成了左中序和右中序数组,后序数组切割成了左后序数组和右后序数组。

接下来,就可以开始递归,代码如下:

root.left=traversal(leftInorder,leftPostorder);
root.right=traversal(rightInorder,rightPostorder);

该题完整代码如下:

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder.length==0 || postorder.length==0) return null;
return findNode(inorder,0,inorder.length,postorder,0,postorder.length);
    }

    public TreeNode findNode(int[] inorder,int inorderStart,int inorderEnd,int[] postorder,int postorderStart,int postorderEnd){
        //第一步
        //if(postorder.length==0 || inorder.length==0) return null;这样写rootvalue那里要超出数组界限,因为没有限制postorderEnd=0时,postorderEnd-1=-1
        if(postorderStart == postorderEnd) return null;//如果后序数组长度为0
        //第二步:找到切割点值
         int rootValue = postorder[postorderEnd-1];
        TreeNode root=new TreeNode(rootValue);
        //叶子节点
        if(postorderEnd==0) return root;
        //第三步:找到切割点在中序数组中的位置
        int delimiterIndex;
        for(delimiterIndex=inorderStart;delimiterIndex<inorderEnd;delimiterIndex++) {
            if (inorder[delimiterIndex]==rootValue) break;
        }
        //第四步:划分中序左右数组(和c++不同,不能直接初始化int[] leftInorder(inorder.begin(),inorder.begin()+delimiterIndex)
         int leftInorderStart=inorderStart;
         int leftInorderEnd=delimiterIndex;
         int rightInorderStart=delimiterIndex+1;
         int rightInorderEnd=inorderEnd;

         //第五步:划分后序左右数组
        int leftPostorderStart=postorderStart;
        int leftPostorderEnd=postorderStart+(delimiterIndex-inorderStart);
        int rightPostorderStart=leftPostorderEnd;
        int rightPostorderEnd=postorderEnd-1;

        //第六步:开始递归
        root.left=findNode(inorder,leftInorderStart,leftInorderEnd,postorder,leftPostorderStart,leftPostorderEnd);
        root.right=findNode(inorder,rightInorderStart,rightInorderEnd,postorder,rightPostorderStart,rightPostorderEnd);

        return root;
        }
    }

从前序与中序遍历序列构造二叉树

例题105:给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

在这里插入图片描述

1.先找到前序中的根节点然后分割中序数组,得到左中序和右中序数组,再分割前序数组得到左右前序数组。整个流程与中序和后序构造二叉树类似。
2.递归时掌握好划分的左右区间。

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
TreeNode root=new TreeNode();
    root=findNode(preorder,0,preorder.length,inorder,0,inorder.length);
    return root;
    }

   public TreeNode findNode(int[] preorder,int preorderStart,int preorderEnd,int[] inorder,int inorderStart,int inorderEnd){
    //第一步
    if (preorderStart == preorderEnd) return null;

    //第二步:找到根节点值
    int rootVal=preorder[preorderStart];//不要用0
    TreeNode root=new TreeNode(rootVal);

    if (preorderEnd - preorderStart == 1) return root;
    //第三步:在中序中找到根节点的下标
    int pos;
    for(pos=inorderStart;pos<inorderEnd;pos++){
        if(inorder[pos]==rootVal) break;
    }

    //第四步:分割中序数组(左开右闭)
    int leftInorderStart=inorderStart;
    int leftInorderEnd=pos;
    int rightInorderStart=pos+1;
    int rightInorderEnd=inorderEnd;//不用-1,区间把握好

    //第五步:分割前序数组
    int leftPreorderStart=preorderStart+1;
    int leftPreorderEnd=leftPreorderStart+pos-inorderStart;
    int rightPreorderStart=leftPreorderEnd;
    int rightPreorderEnd=preorderEnd;//不用-1,区间把握好

    //第六步:递归
    root.left=findNode(preorder,leftPreorderStart,leftPreorderEnd,inorder,leftInorderStart,leftInorderEnd);
    root.right=findNode(preorder,rightPreorderStart,rightPreorderEnd,inorder,rightInorderStart,rightInorderEnd);

    return root;
}
} 

最大二叉树

例题654:给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:

创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树 。

在这里插入图片描述在这里插入图片描述

可通过构造数组来递归,优化为通过下标索引直接在原数组上操作。
每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。
需要注意每次递归数组的左右边界,不再是原始数组的头和尾。
确定递归停止条件,如果不写后续内存会溢出。

class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
TreeNode root = new TreeNode();
        root = traversal(nums, 0, nums.length);
        return root;
    }

    public TreeNode traversal(int[] nums,int left,int right){
 if(left>=right) return null;//必须有停止条件,否则后续会溢出
int mx=nums[left];
int pos=0;
for(int i=left;i<right;i++){
    if(nums[i]>=mx){//注意这里i从left开始,如果left就是最大的需要加=,不然会出错
        mx=nums[i];
        pos=i;
    }
}
TreeNode root=new TreeNode(mx);
//左开右闭,允许空节点进入递归,停止条件为遇到空节点即数组区间为0
//如果不让空节点进入递归,则停止条件为遇到叶子节点
    root.left=traversal(nums,left,pos);//不能写0
    root.right=traversal(nums,pos+1,right);//不能写nums.length,应该是每次开始的数组边界
return root;
    }
}

二叉树周末总结

1.递归中隐藏着回溯
2.判断左叶子,需要相连的三层之间构成约束条件,通过节点的父节点以及孩子结点确定本节点的属性。
3.二叉树的高度与深度的不同。
4.递归函数返回值什么时候需要,什么时候不需要?如果需要搜索整个二叉树则不需要返回值,如果要搜索一条符合条件的路径就需要返回值,因为遇到符合条件的路径了就要及时返回。
5.构造二叉树:前序与中序、中序与后序都能构造唯一的一棵二叉树。
几个注意点:

  • 分割的时候,区间不变原则,左开右闭或者左闭右闭。
  • 2.分割的时候注意前序后者后序已经有一个节点作为中间节点了,后序不再使用。
  • 3.如何使用切割后的前后序数组来切割中序数组?利用切割后的左右数组长度来确定,因为中序和后序数组大小相同。
  • 4.前序与后序无法确定唯一的一棵二叉树,因为没有中序遍历无法确定左右部分,也就无法分割。
    6.构造二叉树时,尽量不要定义新的数组,而是通过下标索引直接在原数组上进行操作,节省时间与空间开销。递归函数什么时候加if,什么时候不加if,其实就是控制空节点是否进入递归。如果让空节点进入递归,就不加if,如果不让空节点进入递归,就加if限制,相应的终止条件也会发生变化。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值