代码随想录刷题——二叉树day18

54 阅读7分钟

第六章 二叉树 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.路径总和

32d88465-effb-49a0-ba9f-5f76ba1e61f9.png

具体代码如下所示:

 /**
     * 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;


        }
    }